Руководство прикладного разработчика#

Термины и определения#

Термин/Аббревиатура

Определение

АС

Автоматизированная система

Heartbeat

Квитанция, передаваемая от получателя к отправителю в ответ на gRPC-вызов, подтверждающая получение сообщения. Не содержит бизнес-информации

CPU

Central Processing Unit, центральный процессор

gRPC

Высокопроизводительный фреймворк, разработанный компанией Google для вызова удаленных процедур (RPC)

Liveness-проба

Программный зонд для определения живучести контейнера. Проба может иметь статус UP - поднята или DOWN - опущена

Readiness-проба

Программный зонд для определения готовности контейнера. Проба может иметь статус UP - поднята или DOWN - опущена

Системные требования#

Требования к системному программному обеспечению приведены в разделе Системные требования документа «Руководство по установке» компонента MQ Gateway (MQGT) продукта Platform V Synapse Enterprise Integration.

Подключение и конфигурирование#

Для установки экземпляра Шлюза MQ на стенд в проект загрузите конфигурационные артефакты:

Артефакт

Содержание

Описание

Deployment

Параметры запуска контейнера приложения в OpenShift

Наименование экземпляра приложения, ссылка на образ контейнера приложения, запрашиваемые ресурсы, публикуемые ports, параметры Liveness- и Readiness-проб, необходимость и параметры подключения sidecar-контейнеров, точки монтирования конфигурационных артефактов в файловую систему контейнера

Config Map

application.yml

Файл, содержащий параметры конфигурации приложения

Secret

ssl-secret.yml

Файл, содержащий параметры настройки SSL. Содержит конфиденциальные данные (пароли к приватным ключам), поэтому загружается в виде секрета

Secret

Ключи и сертификаты

Файлы .jks, содержащие ключи и сертификаты для подключения к менеджерам MQ по SSL. Содержит конфиденциальные данные, поэтому загружается в виде секрета

Service

Артефакт для регистрации приложения в service discovery OpenShift

Селекторы и ports для подключения приложения к механизмам распределения трафика OpenShift

Virtual Service

Артефакт для настройки политик Istio

Параметры маршрутизации трафика между сервисами в OpenShift

Destination Rule

Артефакт для настройки политик Istio

Параметры балансировки трафика между Pods приложения

Service Entry

Артефакт для регистрации внешнего сервиса в OpenShift

Содержит адреса hosts и номера портов для подключения к менеджерам MQ. Если он уже установлен в проекте для необходимых менеджеров Service Entry, загружать его повторно не требуется

Последовательность загрузки артефактов:

  1. Config Map c application.yml.

  2. Secret c ключами.

  3. Service Entry.

  4. Deployment.

  5. Service.

  6. Virtual Service.

  7. Destination Rule.

Порядок действий при загрузке артефактов в проект см. в разделе «Установка» документа «Руководство по установке».

Настройка Шлюза MQ согласно вариантам использования (см.раздел «Варианты использования» документа «Детальная архитектура») подробно описана в разделе «Настройка конфигурации» документа «Порядок настройки Шлюза MQ». Список доступных для настройки параметров приведен в разделе «Верхнеуровневые блоки настроек» документа «Полное описание настроек».

Миграция на текущую версию#

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

  1. Подготовьте Config-map с новой конфигурацией.

  2. Подготовьте артефакт Deployment, в котором замените ссылку на Docker-образ Шлюза MQ в репозитории ссылкой на Docker-образ с текущей версией.

  3. Остановите Шлюз MQ.

  4. Загрузите новую конфигурацию Шлюза MQ.

  5. Загрузите новый Deployment.

  6. Запустите Шлюз MQ (если количество реплик (значение параметра replicas) в Deployment больше 0, Шлюз MQ запустится автоматически).

Дополнительно:

Быстрый старт#

Разработка первого приложения с использованием программного продукта:

  1. Для менеджера MQ Dev-стенда создайте 2 тестовые очереди для входящих и исходящих сообщений (например, TEST.GW.IN и TEST.GW.OUT).

  2. Проверьте наличие и при необходимости загрузите артефакт ServiceEntry, задающий точку подключения.

    В артефакте должны быть указаны параметры менеджера MQ:

    • spec.hosts[].<hostname>;

    • spec.ports[].protocol: tcp;

    • spec.ports[].name: tcp;

    • spec.ports[].number:<port>.

    Service Entry: Service Entry.yml

  3. Подготовьте и загрузите артефакт Config Map c файлом application.yml, устанавливающий настройки Шлюза MQ.

    В артефакте должны быть указаны следующие параметры:

    • режим работы Шлюза MQ:

      • mq.workMode: async;

      • mq.systemType: sp;

    • очереди MQ:

      • mq.connection.receiveQueue: TEST.GW.IN;

      • mq.connection.sendQueue: TEST.GW.OUT;

    • подключения к MQ:

      • mq.connection.connections[].hostname: <hostname>;

      • mq.connection.connections[].port: <port>;

      • mq.connection.connections[].channel: <channel>*;

      • mq.connection.connections[].queueManager: <manager>*;

    • порта Healthcheck:

      • server.port: 8787;

    • сервера и клиента gRPC;

      • grpc.server.serverPort: 5454;

      • grpc.client.settings.default.port: 5454;

    • маршрутизации:

      • routing.routeList[].destinationExpression: '"<имя сервиса клиентского приложения>"'.

    * следующие параметры используются только при работе с провайдером IBM MQ:

    • mq.connection.connections[].channel: <channel>;

    • mq.connection.connections[].queueManager: <manager>;

    Config Map: Config Map.yml

  4. Подготовьте и загрузите артефакт Deployment, устанавливающий параметры запуска контейнера Шлюза MQ.

    В артефакте должны быть заданы параметры:

    • spec.template.spec.volumes[0].configMap.name: <Имя артефакта Config Name>;

    • spec.template.spec.containers[].ports[].containerPort: 5454;

    • spec.template.spec.containers[].ports[].containerPort: 8787;

    • spec.template.spec.containers[].image: <ссылка на Docker-образ Шлюза MQ в целевом репозитории>.

    Deployment: Deployment.yml

  5. Подготовьте и загрузите артефакт Service, регистрирующий сервис в OpenShift.

    В артефакте должны быть заданы параметры:

    • spec.ports[].name: grpc;

    • spec.ports[].protocol: TCP;

    • spec.ports[].port: 5454;

    • spec.ports[].targetPort: 5454.

    Service: Service.yml

  6. Проверьте, что Шлюз MQ запущен и готов к работе:

    1. В консоли OpenShift откройте меню Workloads → Pods, найдите Pod Шлюза MQ. Перейдите по ссылке в наименовании Pod и проверьте, что он имеет статус Running.

    2. Зайдите в терминал Pod и выполните команду:

      sh-4.2$ curl localhost:8799/actuator/health/ping
      {"status":"UP"}
      

      Ответ {"status":"UP"} показывает, что Шлюз MQ запущен и готов к работе.

  7. Подготовьте Proto-файлы с описанием универсального API Шлюза MQ.

    ProtoMessage.proto:

    syntax = "proto3";
    
    import "google/protobuf/any.proto";
    
    package com.sbt.synapse.gateway;
    
    message ProtoMessage {
      string messageId = 1;
      string correlationId = 2;
      string body = 14;
      map<string, string> systemHeaders = 3;
      map<string, string> userHeaders = 4;
      google.protobuf.Any extension = 15;
    }
    

    Heartbeat.proto:

    syntax = "proto3";
    
    package com.sbt.synapse.gateway;
    
    message Heartbeat {
    int64 timestamp = 1;
    string messageId = 2;
    }
    

    MessageService.proto:

    syntax = "proto3";
    
    import "Message.proto";
    import "Heartbeat.proto";
    
    package com.sbt.synapse.gateway;
    
    option java_multiple_files = true;
    option java_package = "com.sbt.synapse.gateway.protobuf";
    option java_outer_classname = "MessageService";
    option objc_class_prefix = "HLW";
    
    service MessageAsyncChannel {
    rpc processMessage (ProtoMessage) returns (Heartbeat);
    }
    
    service MessageSyncChannel {
    rpc processMessage (ProtoMessage) returns (ProtoMessage);
    }
    

    MqMessageInformation.proto:

    syntax = "proto3";
    
    package com.sbt.synapse.gateway.mq;
    
    message MqMessageInformation {
        enum DeliveryMode {
            UNKNOWN_MODE = 0;
            NON_PERSISTENT = 1;
            PERSISTENT = 2;
        }
        enum MessageType {
            UNKNOWN_TYPE = 0;
            REQUEST = 1;
            REPLY = 2;
            REPORT = 4;
            DATAGRAM = 8;
        }
        MessageType messageType = 1;
        int32 priority = 2;
        DeliveryMode deliveryMode = 3;
        int64 expiry = 4;
        string destinationQueue = 5;
        string destinationManager = 6;
        string sourceQueue = 7;
        string sourceManager = 8;
        string jmsType = 9;
    }
    
  8. Создайте проект SpringBoot-приложения.

  9. По proto-описанию сгенерируйте Java-классы, используя proto-компилятор (protoc) или плагин для используемой системы сборки (gradle, maven).

  10. Добавьте полученные файлы к проекту.

    Package

    Classes

    com.sbt.synapse.gateway

    Message
    HeartbeatOuterClass

    com.sbt.synapse.gateway.protobuf

    MessageAsyncChannelGrpc
    MessageSyncChannelGrpc

    com.sbt.synapse.gateway.mq

    MqMessageInformationOuterClass

  11. Дополнительно укажите зависимости:

    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.20.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.20.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-core</artifactId>
      <version>1.20.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty</artifactId>
      <version>1.20.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-services</artifactId>
      <version>1.20.0</version>
    </dependency>
    <dependency>
      <groupId>io.github.lognet</groupId>
      <artifactId>grpc-spring-boot-starter</artifactId>
      <version>2.4.4</version>
    </dependency>
    
  12. Добавьте импорт полученных классов, а также классов поддержки protobuf и grpc.

    Импорт классов:

    import com.google.protobuf.Any;
    import com.sbt.synapse.gateway.HeartbeatOuterClass.Heartbeat;
    import com.sbt.synapse.gateway.protobuf.MessageAsyncChannelGrpc;
    import com.sbt.synapse.gateway.Message.ProtoMessage;
    import com.sbt.synapse.gateway.mq.MqMessageInformationOuterClass.MqMessageInformation;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    import io.grpc.stub.StreamObserver;
    
  13. Добавьте в приложение сервис для приема асинхронных входящих вызовов и отправки подтверждений.

    Пример сервиса:

    @GRpcService
    public class GrpcService extends MessageAsyncChannelGrpc.MessageAsyncChannelImplBase {
    
    @Override
    public void processMessage(ProtoMessage message, StreamObserver<Heartbeat> responseObserver) {
    Logger logger = LoggerFactory.getLogger(GrpcService.class);
    logger.info("Request: " + message.getBody());
    
    responseObserver.onNext(Heartbeat.newBuilder().setMessageId(message.getMessageId()).setTimestamp(System.currentTimeMillis()).build());
    
    responseObserver.onCompleted();
    }
    }
    
  14. Добавьте в приложение клиента, формирующего простой XML-запрос с установленными messageId и Expiry;

    Пример клиента:

    @Component
    public class GrpcClientSender {
    
        @Value("${service.host}")
        private String serviceHost;
    
        @Value("${service.port}")
        private int servicePort;
    
        public void makeRequest() {
            Logger log = LoggerFactory.getLogger(GrpcClientSender.class);
    
            ManagedChannel channel = ManagedChannelBuilder.forAddress(serviceHost, servicePort).usePlaintext().build();
            MqMessageInformation mqMessageInformation = MqMessageInformation.newBuilder().setExpiry(30000).build();
            ProtoMessage message = ProtoMessage.newBuilder()
                    .setBody("<Rq><RqUID>12345678901234567890123456789012</RqUID></Rq>").setExtension(Any.pack(mqMessageInformation)).setMessageId("123456789012345678901234567890").build();
            MessageAsyncChannelGrpc.MessageAsyncChannelBlockingStub messageAsyncChannelBlockingStub = MessageAsyncChannelGrpc.newBlockingStub(channel);
    
            try {
                Heartbeat pongMessage = messageAsyncChannelBlockingStub.processMessage(message);
                log.info("Response: " + pongMessage.getMessageId());
            } catch (Exception e) {
                System.out.println("catch error " + e.getMessage());
            } finally {
                channel.shutdown();
            }
        }
    }
    
  15. В application.properties установите следующие параметры:

    service.host=localhost
    service.port=5454
    server.port=8787
    grpc.port=5454
    
  16. Для публикации образа приложения в docker-registry создайте Dockerfile. В нем укажите, на основе какого базового образа собирать контейнер с приложением, имя jar-файла приложения для установки в контейнер и команду для его запуска.

    Dockerfile:

    #Ссылка на базовый Docker-образ, нужно указать свой
    FROM <базовый образ для сборки>
    
    ADD <jarName>.jar /app/<jarName>.jar
    CMD touch /app/<jarName>.jar
    ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=file:/deployments/config/application.properties","-Djava.security.egd=file:/dev/./urandom","-jar","/app/<jarName>.jar"]
    
  17. Соберите Docker-образ и опубликуйте его в Dev-репозитории.

  18. Настройте артефакты для приложения.

    Config Map:

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: test-app-config
    data:
      application.properties:
        server.port=8787
        grpc.port=5454
        service.host=<имя сервиса шлюза>
        service.port=5454
    

    Deployment:

    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: test-app
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: test-app
      template:
        metadata:
          labels:
            app: test-app
          annotations:
            sidecar.istio.io/inject: 'true'
        spec:
          volumes:
            - name: application-config
              configMap:
                name: test-app-config
                items:
                  - key: application.properties
                    path: application.properties
                defaultMode: 400
          containers:
            - resources:
                limits:
                  cpu: 200m
                  memory: 400Mi
                requests:
                  cpu: 100m
                  memory: 330Mi
              name: test-app
              env:
                - name: PROJECT_NAME
                  valueFrom:
                    fieldRef:
                      apiVersion: v1
                      fieldPath: metadata.namespace
              ports:
                - containerPort: 5454
                  protocol: TCP
                - containerPort: 8787
                  protocol: TCP
              imagePullPolicy: Always
              volumeMounts:
                - name: application-config
                  readOnly: true
                  mountPath: /deployments/config
              terminationMessagePolicy: File
              image: >-
                <ссылка на Docker-образ приложения в dev - репозитории>
          terminationGracePeriodSeconds: 80
    

    Service:

    apiVersion: v1
    kind: Service
    metadata:
      name: test-app
    spec:
      selector:
        name: test-app
      ports:
        - name: grpc
          port: 5454
          targetPort: 5454
    
  19. Загрузите артефакты в OpenShift.

  20. Откройте терминал Pod приложения и проверьте, что в журналах появились записи об успешной отправке сообщений.

    Пример журнала клиента:

    2021-02-07 10:54:27.740 INFO 14592 --- [ main] c.s.s.workshop.grpc.GrpcClientSender : Response: 123456789012345678901234567890
    2021-02-07 10:54:27.743 INFO 14592 --- [ main] c.s.s.workshop.grpc.GrpcClientSender : Response: 123456789012345678901234567890
    2021-02-07 10:54:27.746 INFO 14592 --- [ main] c.s.s.workshop.grpc.GrpcClientSender : Response: 123456789012345678901234567890
    
  21. Проверьте прием входящих сообщений в асинхронном режиме. Для этого поместите сообщение в тестовую очередь входящих сообщений. В журнале приложения должна появиться запись о приеме сообщения.

    Пример журнала сервера:

    2021-02-07 10:54:27.743 INFO 3876 --- [ault-executor-0] c.sbt.synapse.workshop.grpc.GrpcService : Request: <Rq><RqUID>12345678901234567890123456789012</RqUID></Rq>
    2021-02-07 10:54:27.746 INFO 3876 --- [ault-executor-0] c.sbt.synapse.workshop.grpc.GrpcService : Request: <Rq><RqUID>12345678901234567890123456789012</RqUID></Rq>
    2021-02-07 10:54:27.748 INFO 3876 --- [ault-executor-0] c.sbt.synapse.workshop.grpc.GrpcService : Request: <Rq><RqUID>12345678901234567890123456789012</RqUID></Rq>
    

Использование программного продукта#

Типовые схемы включения#

Компонент Synapse Шлюз MQ предназначен для организации взаимодействия внутренних компонентов Synapse (шлюзов между собой и сервисами трансформации) с внешними АС с помощью систем обмена сообщениями (далее - MQ). Для внутренних вызовов между компонентами Synapse используется протокол gRPC. Для сериализации передаваемых сообщений внутри Synapse используется ProtoBuf.

Использование компонента Шлюз MQ позволяет исключить специфичную логику вызова провайдера MQ из внутренних компонентов Synapse.

Шлюз MQ реализован в виде контейнеризированного Java-приложения, запускаемого в среде оркестрации контейнеров RedHat OpenShift 4.X. Для доступа к очередям MQ используется интерфейс JMS.

Шлюз MQ является компонентом платформы Synapse и для полноценной работы требует наличия технологических сервисов платформы, таких как прикладное журналирование, интеграционное журналирование, а также включенного механизма оркестрации сервисов (Service Mesh) Istio, встроенного в OpenShift.

Шлюз MQ поставляется в виде собранного Docker-образа. Загрузка образа в среду исполнения и настройка его параметров для конкретной прикладной задачи выполняется в файлах конфигурации для среды OpenShift. Набор этих файлов и составляет прикладной дистрибутив Шлюза MQ.

Один прикладной дистрибутив (Deployment) Шлюза MQ может обеспечивать работу нескольких независимых интеграционных цепочек при условии, что они не требуют установки взаимоисключающих значений конфигурационных параметров. В противном случае для каждой интеграционной цепочки требуется использовать отдельный Deployment Шлюза MQ с собственным набором параметров.

Типовая упрощенная схема показывает место Шлюза MQ в интеграционной цепочке:

Место Шлюза MQ в интеграционной цепочке Схема. Место Шлюза MQ в интеграционной цепочке

Шлюз MQ может обеспечивать взаимодействие типа «запрос-ответ» как по инициативе внешней системы, так и по инициативе внутренних микросервисов.

Шлюз MQ может подключаться к нескольким менеджерам MQ (при условии, что названия очередей, с которыми работает Шлюза MQ на всех менеджерах, совпадают).

Подключение к нескольким MQ Схема. Подключение к нескольким MQ

Шлюз MQ поддерживает протокол HTTP и позволяет реализовывать взаимодействие MQ-HTTP и HTTP-MQ. Сервис, вызываемый Шлюзом MQ или вызывающий Шлюз MQ по HTTP, может находиться как снаружи, так и внутри OpenShift.

Варианты подключения Шлюза MQ Схема. Варианты подключения Шлюза MQ

Следует иметь в виду, что для корректной работы Шлюза MQ должны быть правильно сформированы все артефакты OpenShift/Istio, обеспечивающие среду его функционирования.

Описание работы Шлюза MQ#

Версия Шлюза MQ определяет собранный и опубликованный образ контейнера приложения. Например, версии 0.2.0.24 и 0.2.0.20 - это разные образы в репозитории. Другие параметры (наименование, тип шлюза и пр.) являются конфигурируемыми и определяются в артефактах Deployment и Config Map при установке в проект OpenShift.

Настроив нужным образом конфигурацию одного и того же образа контейнера Шлюза MQ (например, mq-gateway-0.2.0.24), можно получить прикладные дистрибутивы шлюзов as1-mq-gateway-sc (шлюз-потребитель для АС as1) и as2-mq-gateway-sp (шлюз-поставщик для АС as2).

Шлюзы потребителя и поставщика разделены, так как:

  • имеют принципиальные различия при работе в синхронном режиме;

  • имеют различия в обработке заголовков при работе в асинхронном режиме;

  • имеют различные конфигурации при работе с АС, которая является одновременно потребителем по одним сервисам и поставщиком по другим.

Настройки Шлюза MQ подробно описаны в разделе «Настройка конфигурации» документа «Порядок настройки Шлюза MQ» и «Верхнеуровневые блоки настроек» документа «Полное описание настроек».

Запуск#

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

Параметры подключения к MQ-менеджерам устанавливаются настройками в блоке mq.

Если менеджер очередей недоступен, то Readiness проба Шлюза MQ возвращает значение failed. Если Readiness и Liveness-пробы настроены на общий endpoint (/actuator/health), то в артефакте Deployment для Liveness-пробы должно быть установлено такое значение InitialDelay, которое совместно с periodSeconds и failureThreshold позволят Шлюзу MQ загрузиться и установить соединение с менеджером очередей раньше, чем отсутствие соединения заставит OpenShift перезапустить Шлюз MQ.

Чтобы отключение менеджера не приводило к рестарту Pod Шлюза MQ рекомендуется настраивать Readiness и Liveness-пробы на отдельные endpoints (/actuator/health/readiness и actuator/health/liveness соотвественно). В этом случае liveness-проба не проверяет подключение к менеджерам, и их недоступность не приводит к перезапуску пода.

При проверке доступности менеджеров (в реализации Шлюза MQ на Java, для провайдера IBM MQ) возможно подвисание соединений к MQ, что приводит к задержкам срабатывания reаdiness-пробы (и как следствие — к задержке возврата шлюза в работу при восстановлении доступности менеджера). Для снижения влияния таких подвисаний, требуется установить таймауты на соединение c использованием опций JVM:

  • com.ibm.mq.cfg.MQRCVBLKTO - таймаут ожидания ответа от сервера;

  • com.ibm.mq.cfg.TCP.Connect_Timeout - таймаут на установку соединения.

Их можно задать через переменные окружения в Deployment Шлюза.

Пример:

          env:
            - name: JAVA_TOOL_OPTIONS
              value: >-
                -Dcom.ibm.mq.cfg.MQRCVBLKTO=5 -Dcom.ibm.mq.cfg.TCP.Connect_Timeout=5            

Пока Readiness-проба не поднята, механизмы OpenShift не включают этот Pod Шлюза MQ в балансировку, и трафик на него не поступает. Для Readiness-пробы значения InitialDelay, periodSeconds и failureThreshold должны быть существенно меньше, чтобы не создавать излишних задержек при включении экземпляра шлюза под нагрузку.

Конфигурирование артефакта Deployment описано в разделе «Подготовка общих артефактов OpenShift» документа «Порядок настройки Шлюза MQ».

Передача сообщений от Внешней АС Микросервису (Тип шлюза: шлюз потребителя)#

Задачами шлюза-потребителя являются:

  • получение запроса Внешней АС через систему обмена сообщениями;

  • трансформация запроса в формат protoBuf;

  • отправка Proto-сообщения внутреннему микросервису с помощью вызова по gRPC, поднятого им универсального API;

  • прием ответа от внутреннего микросервиса;

  • трансформация ответа в формат системы обмена сообщениями;

  • отправка ответа через систему обмена сообщениями Внешней АС.

Режим потребителя устанавливается настройкой mq.systemType = sc.

Асинхронный режим работы#

В асинхронном режиме Шлюз MQ не связывает ответ, полученный от микросервиса, с отправленным ему запросом. Передача запроса и ответа происходит независимо друг от друга.

Асинхронный режим устанавливается настройкой mq.workMode = async.

Прием запроса#

При появлении в очереди запросов входящего сообщения Шлюз MQ считывает сообщение из очереди.

Входящая очередь для асинхронных запросов устанавливается настройкой mq.connection.receiveQueue.

Из полученного сообщения Шлюз MQ извлекает значения параметров, определяющих дальнейшую маршрутизацию вызова. Эти параметры могут содержаться как в заголовках, так и в теле сообщения либо задаваться константой. Процесс маршрутизации описан в разделе «Маршрутизация gRPC-вызовов в Шлюзе MQ» текущего документа.

Параметры маршрутизации устанавливаются в секции routing.

На основании данных полученного запроса Шлюз MQ формирует Proto-сообщение и вызывает по gRPC микросервис в OpenShift, определенный по значениям параметров маршрутизации, передавая ему сформированное сообщение. Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в gRPC» документа «Преобразование интерфейсов».

Необходимо иметь в виду, что вызов по gRPC всегда выполняется синхронно. Если в конфигурации Шлюза MQ указан асинхронный режим работы, то это означает, что Шлюз MQ, выполнив вызов по gRPC, получает не результат обработки запроса на стороне поставщика сервиса, а подтверждение того, что запрос успешно получен поставщиком сервиса. Это специальное сообщение - HeartBeat, которое содержит идентификатор исходного сообщения и время его получения.

Отправка ответа#

Чтобы передать информационный ответ через Шлюз MQ, работающий в асинхронном режиме, поставщик сервиса должен самостоятельно инициировать его вызов по gRPC.

gRPC-вызов Шлюза MQ Схема. gRPC-вызов Шлюза MQ

При работе в асинхронном режиме ответ может быть получен любым Шлюзом MQ, а не только тем, который обработал запрос (на схеме Шлюзы MQ отмечены голубым цветом).

Чтобы обеспечить возможность работы в асинхронном режиме, вызываемый микросервис должен поднять асинхронный API. Более подробно API описан в разделе «Интерфейс gRPC» документа «Спецификация интерфейса gRPC».

Для приема ответов от поставщика сервиса Шлюз MQ поднимает асинхронный API gRPC.

При вызове этого API поставщиком Шлюз MQ принимает Proto-сообщение с ответом.

Шлюз MQ преобразует полученное сообщение в MQ-сообщение. Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса gRPC в JMS» документа «Преобразование интерфейсов»

В зависимости от значения параметра sendToCustomDestination в разделе mq-конфигурации Шлюза MQ и информации, полученной во входящем сообщении, Шлюз MQ может передать сообщение:

  • в очередь и менеджер, указанные в настройках шлюза;

  • в очередь и менеджер, указанные в блоке Destination Proto-сообщения;

  • в очередь и менеджер, указанные в gRPC-заголовках входящего вызова.

Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет ответ, описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Если отправка завершилась успешно, Шлюз MQ возвращает Heartbeat в ответе.

Если в процессе отправки возникает ошибка, Шлюз MQ завершает gRPC-вызов ошибкой с кодом 603 (ошибка взаимодействия с MQ).

Синхронный режим работы#

При синхронном режиме работы ответ связан с запросом сессией gRPC-вызова.

Синхронный режим устанавливается настройкой mq.workMode = sync.

Прием запроса#

При появлении в очереди запросов входящего сообщения Шлюз MQ считывает сообщение из очереди.

Входящая очередь для синхронных запросов (mq.workMode=sync) устанавливается настройкой mq.connection.receiveQueue.

Из полученного сообщения Шлюз MQ извлекает значения параметров, определяющих дальнейшую маршрутизацию вызова. Процесс маршрутизации описан в разделе «Маршрутизация gRPC-вызовов в Шлюзе MQ» текущего документа.

На основании данных полученного запроса Шлюз MQ формирует Proto-сообщение. Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в gRPC» документа «Преобразование интерфейсов».

При работе в синхронном режиме Шлюз MQ получает JMS-сообщение, преобразует его в Proto-сообщение и вызывает синхронный API, который должен быть поднят вызываемым сервисом. API подробно описан в разделе «Интерфейс gRPC» документа «Спецификация интерфейса gRPC».

Отправка ответа#

Шлюз MQ ожидает ответ в течение времени тайм-аута, заданного в конфигурации в секции grpc. Для исключения зависания gRPC-соединений при синхронных вызовах необходимо обязательно задавать тайм-аут.

Тайм-аут устанавливается настройкой grpc.client.settings.timeout.

Если в течении этого времени ответ не получен, Шлюз MQ прерывает вызов и записывает тайм-аут в журнал.

В случае успешного завершения вызова, Шлюз MQ получает Proto-сообщение с ответом от поставщика сервиса. В противном случае в журнал записывается ошибка.

Далее, аналогично работе в асинхронном режиме, Шлюз MQ преобразует полученное сообщение в JMS-сообщение и отправляет его менеджеру в очередь, указанную в настройках Шлюза MQ и согласно информации, содержащейся в ответе.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса gRPC в JMS» документа «Преобразование интерфейсов».

Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет ответ, описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Если отправка завершилась неудачей, Шлюз MQ записывает в журнал ошибку.

Синхронный режим работы шлюза-потребителя Схема. Синхронный режим работы шлюза-потребителя

Передача сообщений от Микросервиса Внешней АС (Тип шлюза: шлюз поставщика)#

Задачами шлюза поставщика являются:

  • прием по gRPC запроса внутреннего микросервиса;

  • трансформация запроса в формат системы обмена сообщениями;

  • отправка запроса через систему обмена сообщениями внешней АС;

  • прием ответа от внешней АС через систему обмена сообщениями;

  • трансформация ответа в формат protoBuf;

  • отправка Proto-сообщения внутреннему микросервису;

  • мониторинг и очистка очереди ответов от просроченных сообщений, которые не были считаны.

Режим поставщика устанавливается настройкой mq.systemType = sp.

Асинхронный режим работы#

В асинхронном режиме Шлюз MQ не связывает запрос, отправленный внешней АС, с полученным от нее ответом. Передача запроса и ответа происходит независимо друг от друга.

Асинхронный режим устанавливается настройкой mq.workMode = async.

Отправка запроса#

При старте Шлюз MQ поднимает на своей стороне асинхронный API gRPC.

Асинхронный режим работы шлюза-поставщика Схема. Асинхронный режим работы шлюза-поставщика

Чтобы отправить запрос во внешнюю систему, микросервис внутри OpenShift вызывает gRPC API Шлюза MQ.

Шлюз MQ преобразует полученное сообщение в JMS-сообщение. Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса gRPC в JMS» документа «Преобразование интерфейсов».

В зависимости от настройки sendToCustomDestination в разделе mq-конфигурации Шлюза MQ и информации, полученной во входящем сообщении, Шлюз MQ может отправить сообщение:

  • в очередь и менеджер, указанные в настройках Шлюза MQ;

  • в очередь и менеджер, указанные в блоке Destination Proto-сообщения;

  • в очередь и менеджер, указанные в gRPC-заголовках входящего вызова.

Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет сообщение, подробно описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Если отправка завершилась успешно, то Шлюз MQ возвращает Heartbeat в ответе.

Если в процессе отправки возникает ошибка, то Шлюз MQ завершает gRPC-вызов ошибкой с кодом 603 (ошибка взаимодействия с MQ).

Внешняя АС считывает сообщение из очереди запросов, выполняет свою бизнес-функцию и помещает ответ в очередь ответов.

Прием ответа#

В асинхронном режиме запущенные экземпляры Шлюза MQ выполняют конкурентное считывание из очереди ответов. Ответ на запрос может быть считан не тем экземпляром Шлюза MQ, который обрабатывал исходный запрос (голубая стрелка на схеме).

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

Процесс маршрутизации описан в разделе «Маршрутизация gRPC-вызовов в Шлюзе MQ» текущего документа.

Параметры маршрутизации устанавливаются в секции настроек routing.

Шлюз MQ преобразует JMS-сообщение в Proto-сообщение и отправляет его в определенный по параметрам маршрутизации микросервис, вызывая его асинхронный gRPC API.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в gRPC» документа «Преобразование интерфейсов».

Если в процессе отправки возникает ошибка, то Шлюз MQ завершает gRPC-вызов ошибкой с кодом 603 (ошибка взаимодействия с MQ).

Синхронный режим работы#

В синхронном режиме Шлюз MQ связывает ответ внешней АС с отправленным ей запросом по CorrelId, который должен быть равен MsgId.

Синхронный режим устанавливается настройкой mq.workMode = sync.

Отправка запроса#

В синхронном режиме работы Шлюз MQ при старте поднимает синхронный API gRPC.

Для отправки запроса во внешнюю систему микросервис внутри OpenShift вызывает gRPC API Шлюза MQ. В случае синхронного вызова gRPS-сессия остается активной до получения ответа от поставщика или истечения тайм-аута.

Таймаут устанавливается настройкой mq.sync-receiver.maxProcessingTime.

В режиме синхронного поставщика при отправке сообщения в очередь Шлюз MQ проверяет время жизни (Time To Live, Expiry) отправляемого сообщения, и если оно превышает таймаут установленный на шлюзе, или не ограничено, принудительно устанавливает его равным таймауту.

Шлюз MQ преобразует полученное Proto-сообщение в JMS-сообщение. Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса gRPC в JMS» документа «Преобразование интерфейсов».

В зависимости от значения параметра sendToCustomDestination в разделе конфигурации Шлюза MQ и информации, полученной во входящем сообщении, Шлюз MQ может отправить сообщение:

  • в очередь и менеджер, указанные в настройках шлюза;

  • в очередь и менеджер, указанные в блоке Destination Proto-сообщения;

  • в очередь и менеджер, указанные в gRPC-заголовках входящего вызова.

Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет ответное сообщение, подробно описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Для синхронного ответа внешняя АС-поставщик должна записать значение MsgId запроса в CorrelId ответа. Кроме того, ответное сообщение должно быть считано тем же экземпляром Шлюз MQ, который отправил запрос, поэтому внешняя система должна отвечать по ReplyToQ/ReplyToQMgr.

Очередь для ответа (ReplyToQ) в Proto-сообщении устанавливает микросервис, инициировавший запрос. ReplyToQMgr обычно не заполняется и подставляется клиентской библиотекой/MQ-менеджером.

Внешняя АС-поставщик считывает сообщение из очереди запросов, выполняет бизнес-функцию и помещает ответ в очередь ответов в ReplyToQ/ReplyToQMgr.

В рамках доработки Шлюза MQ для подключения JMS провайдера Artemis были реализованы дополнительные режимы синхронного чтения ответов со всех подключенных менеджеров. Подробнее описано в разделе «Особенности режима синхронного поставщика в кластере Artemis данного документа» текущего документа.

Режимы SHARED_SINGLE, SHARED и SHARED_SELECTOR могут быть использованы и при работе с JMS провайдером IBM MQ.

Прием ответов#

Шлюз MQ проверяет очередь ответов по списку отправленных запросов и при появлении сообщения с нужным CorrelId считывает его из очереди.

Если тайм-аут gRPC-соединения еще не истек, Шлюз MQ преобразует полученный ответ в Proto-сообщение, возвращает его в качестве ответа микросервису-инициатору и завершает сессию.

Если в полученном ответе отсутствует (пустое) тело сообщения, то Шлюз записывает в системный лог предупреждение: "Отсутствует тело в ответе на сообщение <ID сообщения>".

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в gRPC» документа «Преобразование интерфейсов».

Если истек таймаут на Шлюзе MQ или на вызывающем Микросервисе (смотря что произошло раньше), Шлюз MQ закрывает входящее соединение и прекращает ожидание и обработку ответа.

Синхронный режим работы шлюза-потребителя Схема. Синхронный режим работы шлюза-потребителя

Мониторинг и очистка очереди ответов#

Возможна ситуация, когда при синхронном вызове АС-поставщик в ответе не устанавливает значение Expiry (Expiry=-1). Если у АС-поставщика возникают задержки с ответом, превышающие тайм-аут, установленный в настройках шлюза, то эти сообщения не считываются и начинают накапливаться в очереди ответов. Для предотвращения переполнения очередей в Шлюз MQ встроен механизм, который отслеживает наличие просроченных сообщений в очередях: Шлюз MQ проверяет время нахождения сообщения в очереди, и если оно превышает установленный порог, удаляет его.

Параметры мониторинга просроченных сообщений устанавливаются в настройках mq.monitoring.messages.expiry-listener.

Важно! В текущей реализации Шлюз MQ для работы мониторинга просроченных сообщений в настройках шлюза обязательно должен быть сконфигурирован порт сервера gRPC.

Порт сервера gRPC устанавливается настройкой grpc.server.serverPort.

Остановка Шлюза MQ#

При штатной остановке Шлюза MQ:

  1. Прекращается прием новых запросов по каналам gRPC и MQ.

  2. Завершается обработка ранее поступивших сообщений.

  3. Завершается отправка обработанных сообщений.

  4. Приложение Шлюза MQ завершает работу.

Шлюз MQ ожидает остановки listeners MQ-очередей в течение времени, заданного в его настройках.

Время остановки listeners MQ устанавливается настройкой terminationGracePeriodSeconds.

Если приложение не завершает работу за время, указанное в артефакте Deployment, то оно принудительно останавливается сигналом kill механизмами OpenShift.

Время ожидания OpenShift до принудительной остановки приложения устанавливается в Deployment параметром spec.template.spec.terminationGracePeriodSeconds.

Журналирование#

В процессе работы Шлюз MQ фиксирует события в журналах.

Системное журналирование#

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

Вы можете получить доступ к системному журналу с помощью веб-интерфейса OpenShift: WorkloadsPods → имя Pod → вкладка Logs.

Чтобы выгрузить системный журнал через консоль клиента OpenShift, выполните команду:

oc logs -c <имя контейнера> <имя Pod> > <имя файла>.txt

Канал вывода журнала, уровень журналирования и шаблон записи события задается в файле logback.xml. Для изменения настроек по умолчанию в конфигурации шлюза вы можете задать расположение собственного файла logback.xml.

Расположение собственного файла logback.xml устанавливается настройкой logging.config.

Прикладное журналирование#

В прикладном журнале фиксируются следующие шаги прохождения интеграционной цепочки:

  • Шлюз MQ считал сообщение JMS/MQ из очереди внешней системы;

  • Шлюз MQ вызвал микросервис по gRPC и отправил ему сообщение;

  • Шлюз MQ получил сообщение по gRPC от микросервиса;

  • Шлюз MQ отправил JMS/MQ-сообщение в очередь внешней системы;

  • Ошибка на Шлюзе MQ.

Прикладной журнал записывается на смонтированный в контейнер приложения ресурс, который задается в конфигурации шлюза. По умолчанию прикладной журнал записывается в файл /opt/synapse/logs/messages1.log.

Прикладные записи журнала могут быть извлечены из контейнера приложения и отправлены в подсистему журналирования платформы. Для этого в Deployment Шлюза MQ должен быть сконфигурирован Sidecar-контейнер с компонентом платформы Synapse - Агентом журналирования. Порядок подключения и использования Агента журналирования приведено в документе Руководство прикладного разработчика на этот компонент.

Интеграционное журналирование (трассировка)#

Интеграционное журналирование выполняется средствами Service Mesh.

Sidecar-контейнер istio-proxy обеспечивает передачу трассировочной информации из транспортных заголовков каждого исходящего gRPC-вызова в систему интеграционного журналирования.

Шлюз MQ заполняет заголовки трассировки в соответствии с заданными настройками.

Правила заполнения заголовков трассировки устанавливаются в секции настроек tracing.

Горизонтальное масштабирование#

Горизонтальное масштабирование Шлюза MQ реализуется и регулируется системными механизмами OpenShift/Istio. В любой момент, при необходимости, могут работать одновременно произвольное количество экземпляров Шлюза MQ с общей конфигурацией (произвольное количество Pods одного Deployment). Количество запущенных экземпляров определяется настройкой в Deployment и ограничивается только лимитами ресурсов проекта в OpenShift.

Количество запускаемых экземпляров (Pods) Шлюза MQ задается в артефакте Deployment параметром spec.replicas.

Метрики#

Для дополнительного мониторинга параметров в Шлюзе MQ реализован механизм сбора метрик его работы. Метрики публикуются на HTTP-интерфейсе шлюза в формате prometheus и доступны по URI /actuator/prometheus (метод GET).

Порт, на котором запускается HTTP-сервис, задается настройкой server.port конфигурации шлюза.

Вы можете получить текущий актуальный список метрик, выполнив команду в терминале контейнера Шлюза MQ:

curl localhost:<номер http порта шлюза>/actuator/prometheus

Мониторинг сертификатов#

Шлюз MQ в процессе работы проверяет срок действия используемых сертификатов SSL, и публикует метрику certs выводящую эту информацию, вид метрики приведен ниже, Значения параметров указаны для примера и в действующей системе будут отличаться.

certs{certLabel="{Alias='gateway', Subject='CN=GATEWAY', Expiration date='01.03.2122', Issuer='CN=SYNAPSEDEV_ROOT_CA'}",} 364.0
certs{certLabel="{Alias='root-ca', Subject='CN=SYNAPSEDEV_ROOT_CA', Expiration date='26.07.3021', Issuer='CN=SYNAPSEDEV_ROOT_CA'}",} 3649.0

Мониторинг используемых ресурсов#

Для мониторинга утилизируемых Шлюзом MQ ресурсов используются прикладные метрики:

Метрика

Комментарий

system_cpu_usage

использование CPU гостевой ОС контейнера в целом

process_cpu_usage

использование CPU процессом JVM

jvm_memory_used_bytes{area="heap"}

Количество используемой памяти для heap

jvm_memory_used_bytes{area="nonheap"}

Количество используемой памяти кроме heap

jvm_memory_used_bytes

Общее количество используемой памяти

jvm_memory_committed_bytes{area="heap"}

Количество памяти в байтах выданное JVM для heap

jvm_memory_committed_bytes{area="nonheap"}

Количество памяти в байтах выданное JVM кроме heap

jvm_memory_committed_bytes

Общее количество памяти в байтах выданное JVM

jvm_memory_max_bytes{area="heap"}

Максимальное количество памяти для heap в байтах, которое может быть использовано при управлении памятью

jvm_memory_max_bytes{area="nonheap"}

Максимальное количество памяти кроме heap в байтах, которое может быть использовано при управлении памятью

jvm_memory_max_bytes

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

jvm_buffer_memory_used_bytes{id="direct}

Оценка объема памяти, используемой JVM под пул

jvm_threads_live_threads

Количество активных потоков (daemon и non-daemon)

Для расчета производных показателей используются также платформенные метрики публикуемые Kubernetes:

Метрика

Комментарий

container_memory_working_set_bytes

рабочий набор контейнера = RSS+Cache , кеш - уровня ОС и не регулируется приложением

container_spec_memory_limit_byte

лимит памяти контейнера

container_memory_rss

RSS контейнера

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

Для мониторинга ресурсов в системе визуализации (Grafana) можно настроить следующие показатели:

heap %

=sum(jvm_memory_used_bytes{instance="$instance",area="heap"})/sum(jvm_memory_max_bytes{instance="$instance",area="heap"})*100

Важный показатель для контроля заполнения heap и ошибок OOM heap space.

jvm memory

~=sum(jvm_memory_used_bytes{instance="$instance", area="nonheap",id!="miscellaneous non-heap storage"})+sum(jvm_memory_committed_bytes{instance="$instance",area="heap"})+sum(jvm_buffer_memory_used_bytes{application="$app", instance="$instance", id="direct"})+sum(jvm_threads_live_threads{instance="$instance"})*1024*(256*2)

Это примерная память процесса JVM (точно подсчитать нельзя), причем при простое ее сбрасывает ОС или JVM и разрыв между RSS и JVM вырастает.

Container working set %

=max(container_memory_working_set_bytes{pod="$instance", container=~".*gate.*"})/max(container_spec_memory_limit_bytes{pod="$instance", container=~".*gate.*"})*100

Это процент использования памяти контейнера - то, что отображается в стандартном мониторинге.

Container working set

=max(container_memory_working_set_bytes{pod="$instance", container=~".*gate.*"})

Это рабочий набор контейнера

Container rss %

=max(container_memory_rss{pod="$instance", container=~".*gate.*"})/max(container_spec_memory_limit_bytes{pod="$instance", container=~".*gate.*"})*100

Это % RSS от лимита памяти контейнера

Container rss

=max(container_memory_rss{pod="$instance", container=~".*gate.*"})

Это RSS контейнера

В примерах выше переменные содержащие $app - имя Deployment, а $instance - имя Pod шлюза.

Многоточечное подключение#

При многоточечном подключении один Deployment Шлюза MQ может быть подключен к нескольким MQ-менеджерам. При отправке сообщений в исходящие очереди выполняется балансировка по точкам подключения (хост: порт, менеджер, канал).

Важно! Это внутренняя функция Шлюза MQ, которая никак не связана с балансировкой сообщений по менеджерам в кластере MQ.

При этом Шлюз MQ периодически проверяет доступность менеджеров. Если проверка завершается неудачей, менеджер исключается из балансировки до следующей успешной проверки. Из балансировки также исключаются менеджеры отключенные настройкой disabledManagerIds, причем по менеджерам отключенным настройкой, проверка доступности не выполняется. Если доступные менеджеры отсутствуют, то Шлюз MQ опускает Readiness-пробу, и трафик на этот экземпляр (Под) Шлюза MQ блокируется механизмами OpenShift/Istio.

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

Шлюз MQ читает запросы/асинхронные ответы из входящих очередей всех доступных менеджеров.

В синхронном режиме работы Шлюз MQ читает ответ из того же менеджера, через который был отправлен запрос.

Настройки проверки доступности менеджеров устанавливается в секции mq.connection.health конфигурации шлюза. Эта секция имеет приоритет перед настройкой mq.connection.connectionCheckPeriod, которая теперь является устаревшей (deprecated).

В секции mq.connection.health можно задать период опроса, задержку, и, отдельно, количество попыток перед вводом менеджера в балансировку, и выводом из балансировки. Подробнее см. раздел «Верхнеуровневые блоки настроек» документа «Полное описание настроек».

HTTP-интерфейс#

HTTP-интерфейс в Шлюзе MQ предназначен для организации взаимодействия между внешними АС с разными протоколами взаимодействия - MQ и HTTP.

Использование Шлюза MQ с HTTP-интерфейсом позволяет осуществлять вызов MQ/HTTP с возможностью трансформации транспортных заголовков http→mq/mq→http (без преобразования тела сообщения).

Не реализована возможность вызова HTTP→gRPC и gRPC→HTTP.

Определение параметров маршрутизации по телу сообщения работает только с форматами тела сообщения XML и JSON. Возможность работы с другими форматами не поддерживается.

Тип шлюза: шлюз потребителя (MQ→HTTP)#

При появлении в очереди запросов входящего сообщения Шлюз MQ считывает сообщение из очереди.

Из полученного сообщения Шлюз MQ извлекает значения параметров, определяющих дальнейшую маршрутизацию вызова. Эти параметры могут содержаться как в заголовках, так и в теле сообщения, либо задаваться константой. Правила определения параметров маршрутизации устанавливаются в секции настроек routing.

Для вызова по HTTP в параметрах маршрута должен быть задан тип транспорта HTTP. Тип транспорта устанавливается настройкой routing.routeList[].transportType.

Шлюз MQ преобразует полученный запрос в httpMessage и вызывает по HTTP сервис, заданный в параметрах секции HTTP-конфигурации Шлюза MQ, передавая ему сформированное сообщение. Параметры по умолчанию вызываемого сервиса устанавливаются в блоке настроек http.

Параметры вызываемого сервиса для конкретного маршрута устанавливаются в секции настроек routing.routeList[].http.

Важно! В секцию http входят настройки SSL для HTTP-вызова. Эти настройки должны храниться и монтироваться в артефакте типа secret. В отдельный конфигурационный файл можно вынести только настройки SSL по умолчанию из блока http.

Правила преобразования MQ-заголовков в заголовки HTTP сообщения устанавливаются в настройке transform.mq-http.headers.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в HTTP» документа «Преобразование интерфейсов».

В асинхронном режиме после отправки сообщения Шлюз MQ завершает обработку.

В синхронном режиме Шлюз MQ ожидает ответ в течение времени тайм-аута, заданного в конфигурации в секции http. Если в течении этого времени ответ не получен, Шлюз MQ прерывает вызов и записывает в журнал тайм-аут. В случае успешного завершения вызова, Шлюз MQ получает ответ от поставщика сервиса. В противном случае в журнал записывается ошибка.

Шлюз MQ преобразует полученное сообщение в MQ-сообщение. Правила преобразования заголовков HTTP-сообщения в заголовки MQ-сообщения устанавливаются в настройке transform.http-mq.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса HTTP в JMS» документа «Преобразование интерфейсов».

Шлюз MQ отправляет сообщение в менеджер и очередь, определяемые из настроек шлюза и информации, содержащейся в ответе. Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет ответное сообщение, подробно описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Если отправка завершилась неудачей, Шлюз MQ записывает ошибку в журнал.

Тип шлюза: шлюз поставщика (HTTP→MQ)#

Шлюза MQ при запуске поднимает сервис согласно параметрам, указанным в настройках в секции server конфигурации шлюза. Параметры HTTP-сервиса устанавливаются в блоке настроек server. Когда сервис получает HTTP-запрос, Шлюза MQ преобразует его в MQ-сообщение. Правила преобразования заголовков HTTP-сообщения в заголовки MQ-сообщения устанавливаются в секции настроек transform.http-mq.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в HTTP» документа «Преобразование интерфейсов».

В зависимости от значения параметра sendToCustomDestination в разделе mq конфигурации Шлюза MQ и информации, полученной во входящем сообщении, Шлюз MQ может отправить сообщение:

  • в очередь и менеджер, указанные в настройках шлюза;

  • в очередь и менеджер, указанные в блоке Destination Proto-сообщения;

  • в очередь и менеджер, указанные в gRPC-заголовках входящего вызова.

Порядок определения очереди и менеджера, в которые Шлюз MQ отправляет ответ, подробно описан в разделе «Правила определения очереди и менеджера сообщения при отправке сообщения в MQ» текущего документа.

Для синхронного ответа АС-поставщик должна установить значение MsgId запроса в CorrelId ответа. Кроме того, ответ должен быть считан тем же экземпляром Шлюза MQ, который отправил запрос, поэтому внешняя система должна отвечать по ReplyToQ/ReplyToQMgr.

ReplyToQMgr обычно не заполняется и подставляется клиентской библиотекой/MQ-менеджером. Значение ReplyToQ должно быть передано клиентом в запросе.

В асинхронном режиме Шлюз MQ отправляет преобразованное сообщение и завершает обработку.

В синхронном режиме Шлюз MQ отправляет преобразованное сообщение и синхронно ожидает ответ с CorrelId, равным MsgId запроса. Таймаут устанавливается настройкой mq.sync-receiver.maxProcessingTime.

АС-поставщик считывает сообщение из очереди запросов, выполняет бизнес-функцию и помещает ответ в очередь ответов по ReplyToQ/ReplyToQMgr.

Шлюз MQ считывает ответ и преобразует его в HttpMessage. Правила преобразования MQ-заголовков в заголовки HTTP-сообщения задаются в настройке transform.mq-http.headers.

Порядок формирования сообщения подробно описан в разделе «Преобразование интерфейса JMS в HTTP» документа «Преобразование интерфейсов».

Шлюз MQ возвращает преобразованное сообщение инициатору запроса.

Прогрев#

Для снижения латентности (времени обработки сообщений) при запуске нового экземпляра (Pod) Шлюза MQ в нем реализована процедура прогрева.

Перед тем, как Шлюз MQ поднимает Readiness- и Liveness-пробы, он выполняет внутреннюю отправку служебных сообщений. Это позволяет Java-машине оптимизировать исполнение прикладного кода и повысить его быстродействие.

После выполнения процедуры прогрева Шлюз MQ поднимает Readiness- и Liveness-пробы. Шлюз MQ начинает получать рабочий трафик.

Прогрев на Шлюзе MQ включается в настройках секции warmup.

Прогрев не реализован для синхронного режима шлюза-потребителя.

В версии Шлюза MQ 0.2.0.25-AOT используется виртуальная Java-машина OpenJ9 JVM11. OpenJ9 - это виртуальная машина Java, разрабатываемая Eclipse Foundation. Компилятор AOT динамически компилируют методы Java в собственный код AOT во время выполнения и сохраняет их в кеше общих классов (AOT cache). Это позволяет виртуальной машине быстрее запускать приложение при следующем запуске, поскольку ей не нужно тратить время на интерпретацию методов Java.

Шлюз MQ на OpenJ9 - это Docker-образ Java-приложения Шлюза MQ, запущенного на виртуальной машине OpenJ9 с подготовленным AOT-кешем. AOT-кеш был подготовлен при запуске Шлюза MQ на OpenJ9 и прогоне нагрузки через механизм прогрева средствами DevOps при сборке образа перед публикацией в registry.

Работа с ActiveMQ через JMS интерфейс (только в реализации Шлюза MQ на Java)#

Важно! В связи с выпуском целевого компонента — Шлюза ActiveMQ Artemis (ASGT), возможность работы Шлюза MQ (MQGT) c провайдерами MQ (кроме IBM MQ) в будущем будет исключена.

Поскольку Шлюз MQ использует JMS-интерфейс для доступа к системе обмена сообщениями, то его можно использовать для подключения к различным брокерам MQ, поддерживающим спецификацию JMS. Однако для этого требуется встраивать в него классы поддержки соответствующего провайдера JMS.

В текущей версии Шлюз MQ поддерживает работу с брокерами Apache ActiveMQ (classic) далее ActiveMQ и Apache ActiveMQ Artemis далее Artemis.

Один Deployment Шлюза MQ может работать только с одним типом провайдера MQ. Тип провайдера и его параметры устанавливается настройками секций:

mq.typeMQ
mq.connection.connections

Работа с Artemis#

При работе с брокером Artemis Шлюз MQ использует имплементацию JMS интерфейса провайдера Artemis.

Для подключения Шлюза MQ к брокерам Artemis нужно в конфигурации Шлюза MQ задать настройки:

mq:
  typeMQ: ARTEMIS_MQ
  connection:
    connections:
      - hostname: <Имя хоста брокера1>
        port: 64617 # порт с поддержкой ssl. Может быть другой, задается в настройках брокера
      - hostname: <Имя хоста брокера2>
        port: 64617 # порт с поддержкой ssl. Может быть другой, задается в настройках брокера

Шлюз MQ подключается к брокеру Artemis с использованием mTLS. mTLS обеспечивается клиентской библиотекой Artemis.

В настройках подключения не используются параметры channel, так как брокер Artemis не использует такой объект и sslPeerName, так как клиент Artemis проверяет subject сертификата на соответствие hostname.

Секция содержащая пароли для доступа к хранилищам ключей должна размещаться в отдельном файле ssl-secret.yml в артефакте типа Secret и монтироваться в /deployments/config/ssl.

Пример ssl-secret.yml: ssl-secret.yml

Адресация#

Шлюз может подключаться к нескольким очередям для чтения сообщений. Отправка в несколько очередей осуществляется по sendToCustomDestination. Специфика адресации в брокере Artemis состоит в том, что точка назначения определяется адресом и очередью. В случаях, когда имена адреса и очереди совпадают, достаточно указывать только название очереди. Если отличаются, нужно указывать полностью уточненное имя очереди (FQQN) в формате <адрес>::<очередь>. Шлюз поддерживает оба варианта

Кодировка сообщений#

Artemic не обеспечивает механизма перекодирования сообщений и не имеет зарезервированного заголовка для передачи кодовой страницы. Шлюз MQ при работе с брокером Artemis обеспечивает передачу сообщений только в кодировке UTF-8.

Заголовки сообщения#

Соответствие основных заголовков сообщений Artemis структуре ProtoMessage, и, заголовкам сообщений JMS приведено в разделе «Специфика работы с провайдером Apache ActiveMQ Artemis» документа «Преобразование интерфейсов».

Прикладное логирование#

Шлюз MQ при работе с Artemis использует реализацию прикладного логирования "в стиле IBM". То есть в логе, в поле message, в блоке HEADERS по-прежнему остаются блоки MQMD и MQRFH2 (несмотря на фактическое отсутствие таковых в сообщении Artemis), куда группируются заголовки и свойства сообщения по их размещению в структуре ProtoMessage.

Работа с кластером Artemis#

Для асинхронных режимов работа в кластере не отличается от работы с множеством брокеров, не связанных в кластер, так как в этом случае запросы и ответы балансируются по Pods независимо друг от друга.

Важно! При работе с провайдером ActiveMQ и Artemis объект Destination, задающий точку назначения включает только очередь (destinationQueue).

Расширенные режимы работы синхронного поставщика#

Шлюз MQ реализует три дополнительных варианта работы в синхронном режиме:

1. Режим динамического создания очередей для ответов для каждого Pod. (не работает c провайдером IBM в реализации Шлюза MQ на Java)

В этом режиме Шлюз MQ при старте каждого Pod создает отдельную очередь связанную с именем Pod.

При отправке запроса АС-поставщику конкретный Pod Шлюза MQ в заголовке ReplyToQ передает имя "своей" очереди для ответа.

АС поставщик адресует ответ в указанную очередь.

Ответ считывается тем же подом Шлюза, который отправлял запрос.

В этом варианте Pod Шлюза MQ должен проверять ответы на каждом менеджере MQ, к которому он подключен, но поскольку он работает только со своими очередями, чтение по селектору не требуется.

Динамические очереди именуются на основе имени Pod.

Для провайдера IBM:

Для создания динамических очередей на брокере должна быть создана Model Queue. Для целей многопоточной вычитки у этой очереди должен быть установлен режим Shared:

DEFINE QMODEL('DYN') DEFSOPT(SHARED) DEFTYPE(SHAREDYN) REPLACE 

Добавлены настройки модели и разделителя для имени очереди:

mq:
  syncReceiver:
    modelName: DYN
    dynamicQNameSeparator: .DQ. 

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

Имя очереди формируется так:

mq.syncReceiver.defaultReceiveQueue + mq.syncReceiver.dynamicQNameSeparator + значимая_часть_имени_пода_без_дефисов_с_точкой.

Для пода "go-gateway-5dddcc6455-vd4j9" и конфигурации вида:

mq:
  syncReceiver:
    defaultReceiveQueue: RESPONSE
    dynamicQNameSeparator: .DQ.

имя очереди для ответа получит вид RESPONSE.DQ.5dddcc6455.vd4j9

Для провайдера Artemis:

{ADDRESS}::{PODNAME}, где ADDRESS - имя адреса из настройки mq.sync-receiver.defaultReceiveQueue или из заголовка ReplyToQ, если он есть, PODNAME - имя Pod. В этом режиме заголовок ReplyToQ входящего по gRPC сообщения не должен содержать FQQN очереди. То же касается и параметра mq.sync-receiver.defaultReceiveQueue конфигурации Шлюза MQ.

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

<address-setting match="<адрес>#">
    <auto-create-queues>true</auto-create-queues>
    <auto-delete-queues>true</auto-delete-queues>
    <auto-delete-queues-delay>30000</auto-delete-queues-delay>
    <auto-delete-queues-message-count>0</auto-delete-queues-message-count>
</address-setting>
  • автоматическое создание очередей auto-create-queues - по умолчанию включено, убедиться что не выключено явно;

  • автоматическое удаление созданных автоматически очередей auto-delete-queues - по умолчанию включено, убедиться что не выключено явно;

  • таймаут, после которого очередь будет удалена при отсутствии потребителей и сообщений - auto-delete-queues-delay. Рекомендуется ставить достаточный интервал на случай перезапуска Envoy, либо других sidecars, во избежание пересоздания одной и той же очереди. Так же на таймаут влияет глобальный параметр брокера address-queue-scan-period интервал сканирования очередей на удаление. Например, при настройке auto-delete-queues-delay=0 и address-queue-scan-period = 30000 пустая очередь может удалиться в промежутке от 0 до 30 секунд.

  • Количество сообщений в очереди должно быть меньше или равно указанному в auto-delete-queues-message-count чтобы очередь могла быть удалена. Если в настройке задано значение -1, то проверка на наличие сообщений не производится. Если выставить таймаут на удаление очереди равным общему таймауту на шлюзе, то в этом случае потерь сообщений не будет (так как оставшиеся в очереди сообщения все равно не будут обработаны из-за истечения таймаута).

    Разрешения на работу с такими очередями устанавливаются в настройках безопасности для адреса (можно применять wildcard-синтаксис):

    <security-settings>
        <security-setting match="TEST.QUEUE.RESPONSE">
    
            <!-- Очередь для ответов  -->
            <permission type="createNonDurableQueue" roles="amq, gwsp"/>
            <permission type="createDurableQueue" roles="amq, gwsp"/>
            <permission type="consume" roles="amq, gwsp"/>
            <permission type="browse" roles="amq, gwsp"/>
            <permission type="send" roles="amq, assp"/>
            <!-- we need this otherwise ./artemis data imp wouldn't work -->
             <permission type="manage" roles="amq"/>
        </security-setting>
    
        <security-setting match="TEST.QUEUE.REQUEST">
            <!-- Очередь для запросов -->
            <permission type="consume" roles="amq, assp"/>
            <permission type="browse" roles="amq, assp"/>
            <permission type="send" roles="amq, gwsp"/>
            <!-- we need this otherwise ./artemis data imp wouldn't work -->
             <permission type="manage" roles="amq"/>
        </security-setting>
    </security-settings>
    

    В текущем примере gwsp - роль, созданная в брокере для шлюза, может создавать очереди и читать из них на адресе TEST.QUEUE.RESPONSE, но не может в них писать. Для очередей на адресе TEST.QUEUE.REQUEST наоборот, есть права на запись, но нет на чтение. assp - роль для АС, обладает противоположными правами - только читать из очередей на TEST.QUEUE.REQUEST и только писать в очереди на адресе TEST.QUEUE.RESPONSE. Создавать очереди сама не может.

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

2. Режим синхронного чтения ответов из всех подключенных брокеров по корреляционному идентификатору

В этом случае используется схема адресации с одной очередью на адрес. Сценарий:

  • при отправке запроса АС-поставщику конкретный Pod Шлюза MQ в заголовке ReplyToQ передает имя общей очереди для ответа;

  • АС поставщик отвечает в эту очередь, не уточняя имя брокера или имя Pod шлюза;

  • Конкретный Pod Шлюза читает очереди ответов на всех подключенных брокерах, но считывает только "свои" сообщения, используя селектор по корреляционному идентификатору (JMSCorrelationID).

3. Режим синхронного чтения ответов из всех подключенных брокеров по селектору

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

В этом случае используется схема адресации с одной очередью на адрес. Сценарий:

  • при отправке запроса АС-поставщику конкретный Pod Шлюза MQ передаёт:

    • в заголовке ReplyToQ имя общей очереди для ответа.

    • в заголовке GWResponseSelector значение для селектора, по которому текущий Pod читает сообщения из очереди для ответа.

  • АС поставщик отвечает в эту очередь, не уточняя имя брокера или имя Pod шлюза, но обязательно сохраняет и передаёт в сообщении заголовок GWResponseSelector и его значение

  • Конкретный Pod Шлюза читает очереди ответов на всех подключенных брокерах, но считывает только "свои" сообщения, используя селектор по GWResponseSelector.

    В качестве значения селектора используется только имя Pod в заголовке GWResponseSelector

    Используемый заголовок можно поменять через настройку mq.sync-receiver.selectorName.

Выбор режима работы:

Выбор осуществляется настройкой:

mq:
  sync-receiver:
   replyToMode: UNIQUE #SHARED # SHARED_SINGLE # SHARED_SELECTOR
   #selectorName: GWResponseSelector # для режима SHARED_SELECTOR

где UNIQUE - вычитка из динамической очереди (п.1), SHARED и SHARED_SINGLE - вычитка по CorrelId (п.2) из всех или одного менеджера (в который был отправлен запрос) соотвественно, SHARED_SELECTOR - вычитка из всех менеджеров по селектору (п.3)

Для сохранения обратной совместимости по умолчанию используется режим SHARED_SINGLE.

Режим ALL - совмещенный синхронный и асинхронный режим#

Начиная с версии 0.2.0.17 Шлюз MQ может работать в совмещенном (синхронном и асинхронном) режиме. Для этого предусмотрен режим работы ALL.

Совмещенный режим устанавливается настройкой mq.workMode = all.

Система-потребитель может отправлять запросы в две очереди - для синхронных и для асинхронных запросов. Шлюз MQ в режиме потребителя слушает обе очереди. При получении асинхронного запроса он вызывает микросервис-поставщик асинхронно, как описано выше при асинхронном взаимодействии. Отправку ответов микросервис поставщика выполняет самостоятельно, вызывая асинхронный API Шлюза MQ.

Входящая очередь для асинхронных запросов устанавливается настройкой mq.connection.receiveQueue. Входящая очередь для синхронных запросов устанавливается настройкой mq.connection.receiveQueueSync.

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

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

Очередь по умолчанию для синхронных ответов устанавливается настройкой mq.sync-receiver.defaultReceiveQueue.

При получении вызова от микросервиса по асинхронному API Шлюза MQ отправляет полученное сообщение в очередь поставщика и завершает вызов, возвращая потребителю HeartBeat, как было описано ранее при асинхронных взаимодействиях. Ответ в этом случае поставщик отправляет через отдельную очередь для асинхронных ответов, которую постоянно слушает Шлюз MQ.

Валидация сообщений на Шлюзе MQ#

Валидация сообщений на Шлюзе MQ выполняется внешним компонентом (Валидатор запросов IGEG (REQV)).

Возможна валидация как входящих (из MQ) так и исходящих (в MQ) сообщений в XML и JSON форматах.

Для синхронных вызовов можно настроить отправку информационных ответов об ошибках валидации, если форматы (схемы) ответных сообщений это позволяют.

Схема включения валидатора#

mq-gateway_with_validator.png Схема. Схема включения валидатора

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

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

Чтобы Валидатор мог определить схему сообщения для валидации, ему в заголовках HTTP-вызова должны быть переданы два параметра: metod и path. Значение заголовка path на шлюзе определяется динамически, на основании параметров сообщения, по правилам заданным в настройках шлюза. Соответствие значений path схемам сообщений задается в конфигурации валидатора.

Важно!

При работе валидатора совместно со Шлюзом MQ:

Заголовок metod всегда имеет значение POST.

Валидатор, при ошибке валидации должен всегда возвращать код 403 (используется по умолчанию).

Возвращаемые валидатором заголовки error-headers Шлюзом MQ не обрабатываются.

Шлюз MQ при вызове валидатора самостоятельно заполняет заголовок x-request-id значением RqUID определенным для целей логирования.

Подключение валидатора#

Для подключения валидатора необходимо в Deployment Шлюза MQ добавить контейнер валидатора, а также тома с его конфигурацией (артефакт ConfigMap или Secret содержащий файл appl.yml) и схемами сообщений (артефакт ConfigMap или Secret содержащий схемы сообщений) и их точки монтирования.

Ссылку на актуальный образ можно получить в описании Валидатора.

Пример подключения валидатора в Deployment Шлюза MQ: Deployment: spec.template.spec.containers

      containers
        - name: validator
          resources:
            limits:
              cpu: 200m
              memory: 160Mi
            requests:
              cpu: 100m
              memory: 150Mi
          terminationMessagePath: /dev/termination-log
          env:
            - name: PROJECT_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
          ports:
            - containerPort: 50051
              protocol: TCP
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: validator-schemas
              readOnly: true
              mountPath: /schemas
            - name: validator-config
              mountPath: /config
            - name: synapselogs
              mountPath: /opt/synapse/logs
          terminationMessagePolicy: File
          image: >-
            registry.example.com/ci01994970/ci03302438_synapse-igeg/rhel7-java-synapse-sidecar-validator-ci03302438:0.1.0.26

Deployment: spec.template.spec

      volumes:
        - name: validator-config
          configMap:
            name: validator-config
            defaultMode: 256
        - name: validator-schemas
          configMap:
            name: validator-schemas
            defaultMode: 256

В настройках Валидатора нужно задать порт, на котором Валидатор будет принимать запросы. Порт валидатора по номеру не должен совпадать с портом, который использует Шлюз MQ. Также в настройках задается соответствие параметров вызова валидатора конкретной схеме валидации.

Пример настройки Валидатора: validator-config

  app.yaml: |
    port: 8787
    log_level: info
    schemas:
      - name: 'foo'
        method: POST
        path: '/ping'
        schema: /schemas/foo.json
        type: 'json'
        error_response: '{"error-message":{"x-request-id": "{{get .Header "x-request-id"}}", "error": "{{get .Error "error_message"}}"}}'
        error_headers:
          - key: content-type
            value: application/json
          - key: foo
            value: '{{get .Header "foo"}}'
        headers:
          - key: x-foo-first
            value: foo1
        error_code: 200
      - name: bar
        method: POST
        path: '/'
        schema: /schemas/bar.xsd
        type: 'xml'
        error_code: 403
        error_response: '<error_rs>{{get .Error "error_message"}}</error_rs>'
      - name: bar5
        method: POST
        path: '/inbound/sync/rq/bar'
        schema: /schemas/barn.xsd
        type: 'xml'
        error_response: '<error_rs>{{get .Error "error_message"}}</error_rs>'
      - name: bar6
        method: POST
        path: '/outbound/sync/rq/bar'
        schema: /schemas/bar.xsd
        type: 'xml'
        #error_response: '<error_rs>{{get .Error "error_message"}}</error_rs>'
      - name: bar7
        method: POST
        path: '/inbound/sync/rs/bar'
        schema: /schemas/bar.xsd
        type: 'xml'
        #error_response: '<error_rs>{{get .Error "error_message"}}</error_rs>'
      - name: bar8
        method: POST
        path: '/outbound/sync/rs/bar'
        schema: /schemas/bar.xsd
        type: 'xml'
        #error_response: '<error_rs>{{get .Error "error_message"}}</error_rs>'
      - name: bar9
        method: POST
        path: '/inbound/async/_/bar'
        schema: /schemas/bar.xsd
        type: 'xml'
      - name: bar10
        method: POST
        path: '/outbound/async/_/bar'
        schema: /schemas/bar.xsd
        type: 'xml'
      - name: pass-path
        method: GET
        path: '/json'
        schema: ''
        type: 'pass'

Дополнительно, к валидатору, в точку монтирования /schemas должен быть смонтирован артефакт типа ConfigMap или Secret, содержащий схемы для валидации сообщений.

Пример артефакта со схемами: validator-schemas

  bar.xsd: |
    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:element name="address">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="RqUID" type="xs:string"/>
                    <xs:element name="street" type="xs:string"/>
                    <xs:element name="street-number" type="xs:string"/>
                    <xs:element name="city" type="xs:string"/>
                    <xs:element name="zip" type="xs:string"/>
                    <xs:element name="country" type="xs:string"/>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xs:schema>
  barn.xsd: |
    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:element name="address">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="RqUID" type="xs:string"/>
                    <xs:element name="street" type="xs:string"/>
                    <xs:element name="street-number" type="xs:string"/>
                    <xs:element name="city" type="xs:string"/>
                    <xs:element name="zip" type="xs:string"/>
                    <xs:element name="country-n" type="xs:string"/>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xs:schema>
  foo.json: |
    {
      "$id": "http://json-schema.org/draft/2019-09/json-schema-core.html",
      "$schema": "https://json-schema.org/draft/2019-09/schema",
      "$comment": "sample comment",
      "title": "Config dump",
      "type": "object",
      "properties": {
        "configs": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "@type": {
                "type": "string"
              },
              "last_updated": {
                "type": "string"
              },
              "bootstrap": {
                "type": "object",
                "properties": {
                  "admin": {
                    "type": "object"
                  },
                  "dynamic_resources": {
                    "type": "object"
                  },
                  "node": {
                    "type": "object"
                  },
                  "static_resources": {
                    "type": "object"
                  },
                  "stats_config": {
                    "type": "object"
                  }
                },
                "required": ["admin", "dynamic_resources", "node", "static_resources", "stats_config"]
              }
            },
            "required": ["@type", "last_updated", "bootstrap"]
          }
        }
      },
      "required": [
        "configs"
      ]
    }
  foon.json: |
    {
      "$id": "http://json-schema.org/draft/2019-09/json-schema-core.html",
      "$schema": "https://json-schema.org/draft/2019-09/schema",
      "$comment": "sample comment",
      "title": "Config dump",
      "type": "object",
      "properties": {
        "configs": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "@type": {
                "type": "string"
              },
              "last_updated-n": {
                "type": "string"
              },
              "bootstrap": {
                "type": "object",
                "properties": {
                  "admin": {
                    "type": "object"
                  },
                  "dynamic_resources": {
                    "type": "object"
                  },
                  "node": {
                    "type": "object"
                  },
                  "static_resources": {
                    "type": "object"
                  },
                  "stats_config": {
                    "type": "object"
                  }
                },
                "required": ["admin", "dynamic_resources", "node", "static_resources", "stats_config"]
              }
            },
            "required": ["@type", "last_updated-n", "bootstrap"]
          }
        }
      },
      "required": [
        "configs"
      ]
    }

Настройка шлюза для выполнения валидации#

Включить валидацию#

Добавить в конфигурацию Шлюза MQ блок параметров верхнего уровня validator. Параметры могут быть добавлены в существующий файл конфигурации application.yml, но учитывая их критичность, можно выделить их в отдельный файл validator-secret.yml, и поместить его в артефакт типа Secret.

Отдельный артефакт необходимо подключить к контейнеру шлюза в Deployment: Пример подключения секрета с настройками валидации

      volumes:
        - name: test-gw-validator
          secret:
            secretName: test-gw-validator
            defaultMode: 256
      . . .
      containers
        - name: test-gw
          volumeMounts:
            - name: test-gw-validator
              mountPath: /deployments/config/validator

Установить настройку validator.enabled в true.

Установить параметры вызова валидатора#

Установить настройки validator.http.client.url и validator.http.client.timeout

URL нужно задать в формате http://localhost:<port>/check, где port соответствует номеру порта заданному в настройках валидатора.

Установить правила выбора профиля настроек#

Заполнить секцию validator.serviceName. Секция задает правило выбора профиля настроек валидации по параметрам сообщения. Принцип заполнения такой же, как для секции routing, но дефолтный профиль не предусмотрен.

Обычно (но не обязательно) профиль соответствует наименованию сервиса, и может быть получен, например, из пользовательского заголовка ServiceName (если он используется) либо из тега ServiceName в теле сообщения.

Пример заполнения секции validator.serviceName

      serviceName: # правила определения схем для сообщений
        valueFrom:
          - type: fromRfh2Header
            value: ServiceName
          - type: fromBody
            value: //ServiceName
            valueJson: $..ServiceName
        expr:
Установить правила выбора пути для поиска схемы#

Заполнить секцию validator.serviceList. Секция задает список профилей, и для каждого профиля - правила вычисления значения заголовка path для вызова валидатора. Принцип заполнения такой же как для секции routing. Значение path определяется как результат вычисления выражения заданного в pathExpression над списком переменных. Значения переменных определяются из параметров конкретного сообщения по заданным в профиле правилам.

В большинстве случаев (но не всегда) достаточно привязать path к корневому тегу сообщения:

Пример заполнения секции serviceList

      serviceList:
        - serviceName: testServiceName
          pathExpression: ServiceNameRootTag
          variables:
            - name: ServiceNameRootTag
              valueFrom:
                - type: fromBody
                  value: local-name(/*)
              expr:
предопределенные переменные#

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

  • direction - направление движения сообщения. варианты:

    • outbound - в MQ

    • inbound - из MQ

  • workMode - режим вызова, варианты:

    • sync - сообщение получено в синхронном вызове

    • async - сообщение получено в асинхронном вызове

  • msgType - тип сообщения (определяется только для синхронных вызовов), варианты:

    • rq - синхронный запрос

    • rs - ответ на синхронный запрос

    • "_" - сообщение в асинхронном вызове

Пример использования предопределенных переменных

      serviceList:
        - serviceName: testServiceName
          pathExpression: ("/"+variables['direction']+"/"+variables['workMode']+"/"+variables['msgType']+"/"+ServiceNameRootTag)
          variables:
            - name: ServiceNameRootTag
              valueFrom:
                - type: fromBody
                  value: local-name(/*)
              expr:
Заголовки#

В вызов валидатора могут быть добавлены дополнительные заголовки. Они используются для уточненного поиска схемы, а также для параметризации ответа об ошибке валидации (подробности см. в разделе "Параметры настройки" документа "Руководство оператора" на компонент Request Validator (REQV) Platform V Synapse Service Mesh).

Для добавления заголовка в вызов нужно для конкретного профиля настроек заполнить параметр headers:

Пример настройки заполнения заголовков

      serviceList:
        - serviceName: testServiceName
          headers: # Заполняется, если требуется передать дополнительные заголовки для вставки в ответ.
            RqUID:
              - type: fromBody
                value: //RqUID
                # Matching values
                valueJson: $..RQID
          pathExpression: ("/"+variables['direction']+"/"+variables['workMode']+"/"+variables['msgType']+"/"+ServiceNameRootTag)
          variables:
            - name: ServiceNameRootTag
              valueFrom:
                - type: fromBody
                  value: local-name(/*)
              expr:

Выполнение валидации на шлюзе#

Если на шлюзе включена валидация, то к каждому проходящему через шлюз сообщению применяются правила определения профиля валидации.

Если профиль найден, то вычисляется параметр path по правилам заданным в профиле настроек, и тело сообщения отправляется на валидацию.

Если профиль для сообщения не задан, то, по умолчанию, сообщение будет признано валидным и отправлено дальше по маршруту. (Это поведение можно изменить установкой параметра настройки invalidateByDefault в true)

При положительном результате валидации сообщение будет оправлено дальше по маршруту.

При отрицательном результате валидации, будет вызвано исключение. Если вызов был синхронным и валидатор вернул информационный ответ об ошибке, этот ответ будет возвращен инициатору. В остальных случаях:

  • для входящего сообщения из MQ обработка прекращается

  • для входящих по grpc/http - возвращается исключение и статус/код завершения соответственно.

Возврат ответа#

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

  • для синхронного запроса из MQ ответ отправляется в очередь и менеджер указанные в заголовках MQMD.ReplyToQ и MQMD.ReplyToQMgr входящего запроса. По умолчанию заголовок MQMD.CorrelId ответа заполняется значением заголовка MQMD.MsgId запроса (стандартное поведение, может быть изменено установкой параметра replyByCorrelId в true)

  • для ответа на синхронный запрос из MQ тело ответа заменяется полученным от валидатора сообщением, заголовки не модифицируются.

  • Для вызовов по grpc/http ответ возвращается в вызове.

Вызов завершается штатно, исключение не вызывается.

Логирование и мониторинг#

Шлюз MQ фиксирует в прикладном логе заголовок PATH и результат вызова валидатора. Тело сообщения по-умолчанию не логируется (поведение можно изменить установкой параметра настройки loggingBody в true).

Шлюз отображает в метрике errors_gateways количество ошибок валидации и количество ошибок вызова валидатора раздельно по кодам 604 и 605 соответственно.

Ошибки валидации на шлюзе:

ошибка

Описание

код

http статус

grpc статус

ошибка валидации сообщения

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

604

403 Forbidden

aborted

ошибка вызова валидатора (синхронные запросы и асинхронные вызовы)

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

605

502 Bad gateway Есть возможность повтора вызова через другой экземпляр шлюза (если настроены повторы)

unavailable Есть возможность повтора вызова через другой экземпляр шлюза (если настроены повторы)

ошибки вызова валидатора (ответы на синхронные запросы)

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

605

400 Bad request Не выполняется повтор вызова, чтобы избежать дублирования запроса

internal Не выполняется повтор вызова, чтобы избежать дублирования запроса

Хранение секретов в SecurityManager#

Важно! Получение пространства в хранилище секретов и соответствующих доступов выполняется в соответствии с "Руководством для пользователей Secret Management".

В данной инструкции не рассматривается вопрос выпуска ключей и сертификатов.

Общие параметры настройки#

Для обеспечения взаимодействия с SecMan требуется добавить через Annotations deployment Шлюза инъекцию sidecars SecMan и монтирование секретов в виде файлов.

Шлюз MQ использует хранилище сертификатов в формате JKS (CMS в реализации на Golang), в то время как хранилище секретов SecMan не поддерживает хранение файлов. Для целей интеграции необходимо файл хранилища сертификатов предварительно закодировать в Base64.

Полученное значение и содержимое ssl-secret.yml необходимо указать в секрете в хранилище SecMan.

Сами секреты в хранилище могут выглядеть так:

Пример секрета для Java-Шлюза

{
  "config": "mq:\n  sslConfigs:\n    - sslConfigName: TestConfig\n      sslCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\n      sslFipsRequired: false\n      sslResetCount: 0\n      sslSocketFactory:\n        protocol: TLSv1.2\n        keyStore: /mnt/secrets/keystore.jks\n        keyStorePassword: <Пароль для keystore>\n        keyStoreType: JKS\n        trustStore: /mnt/secrets/keystore.jks\n        trustStorePassword: <Пароль для traststore>\n        trustStoreType: JKS\n        sslCertAlias: gateway",
  "keystore": "/u3+7........soDc",
  "masking": "logger:\n  mask:\n    enabled: true\n    cardNumElements: CardNum\n    fioElements: FIO,FirstName,MiddleName\n    elements: Addr3",
  "validator": "validator:\n  enabled: false\n  invalidateByDefault: false",
  "ttl": "3m" # время жизни секрета - определяет период ротации секрета в Pod
}

Здесь:

keystore - представление в Base64 файла keystore.jks

config - содержимое файла ssl-secret.yml (пробелы важны)

masking - содержимое файла masking-secret.yml (пробелы важны)

validator - содержимое файла validator-secret.yml (пробелы важны)

Пример секрета для Go-Шлюза

{  
  "config": "mq:\n  sslConfigs:\n    - sslConfigName: TestConfig\n      sslCipherSuite: ECDHE_RSA_AES_128_GCM_SHA256\n      sslFipsRequired: false\n      sslResetCount: 0\n      sslSocketFactory:\n        protocol: TLSv1.2\n        keyStore: /mnt/secrets/key.kdb\n        sslCertAlias: gateway",
  "key.kdb": "/u3+7........soDc",
  "key.sth": "ZqtuG3........33==",
  "masking": "logger:\n  mask:\n    enabled: true\n    cardNumElements: CardNum\n    fioElements: FIO,FirstName,MiddleName\n    elements: Addr3",
  "validator": "validator:\n  enabled: false\n  invalidateByDefault: false",
  "ttl": "3m" # время жизни секрета - определяет период ротации секрета в Pod
}

Здесь:

key.kdb - представление в Base64 файла key.kdb

key.sth - представление в Base64 файла key.sth

config - содержимое файла ssl-secret.yml (пробелы важны)

masking - содержимое файла masking-secret.yml (пробелы важны)

validator - содержимое файла validator-secret.yml (пробелы важны)

Имена полей можно указать любые, они используются в аннотациях deployment.

Важно! - если в именах полей есть "." (точка), то в template в deployment надо указывать команду index для их чтения:

Index для полей с точкой

{{ index .Data.masking }}

Параметры deployment Шлюза MQ#

В аннотациях агента SecMan применяются Consul Template, основанные на go-template.

Важно!: при inject секретов, особенно кодированных в base64, важно строго соблюдать нотацию и указывать символ "-" в начале и конце управляющих конструкций (как в примерах ниже). Иначе переносы строк и интервалы будут восприняты как текстовое содержимое секретов и добавлены в секрет, что приведёт к некорректной записи файлов при декодировании base64

Без Istio

В deployment Шлюза MQ добавляются следующие аннотации и метки, обеспечивающие подключение агента SecMan и монтирование секретов из него:

Пример конфигурации Deployment для Java-Шлюза

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      labels
        secman-injector: enabled #обеспечивает подключение подов к SecMan

      annotations:
        # базовые аннотации
        vault.hashicorp.com/agent-init-first: "true" # смонтировать секреты до запуска основного контейнера
        vault.hashicorp.com/agent-inject: "true" # обеспечивает инъекцию init-контейнера и Sidecar Vault-Agent в поды Шлюза MQ
        vault.hashicorp.com/namespace: CIxxxxxxxx_CIxxxxxxxx #имя пространства в хранилище секретов формата CIxxxxxxxx_CIxxxxxxxx
        vault.hashicorp.com/agent-pre-populate: "true" #обеспечить наличие секретов до запуска основного контейнера
        vault.hashicorp.com/role: ciNNNNNNNN_xxx # роль в SecMan, получаемая при подключении к хранилищу (по тикету в Jira)
        vault.hashicorp.com/agent-volumes-default-mode: "0400" # задание прав на созданные файлы с секретами в примонтированном EmptyDir

        # подключение секрета настроек маскирования сообщений
        vault.hashicorp.com/secret-volume-path-masking-secret.yml: /deployments/config/masking # путь монтирования файла конфигурации маскирования из секрета
        vault.hashicorp.com/agent-inject-secret-masking-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, masking-secret.yml - имя секрета
        vault.hashicorp.com/agent-inject-file-masking-secret.yml: masking-secret.yml # имя файла конфигурации маскирования из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-masking-secret.yml: |    # шаблон извлечения секрета конфигурации маскирования
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}   #полный путь к секрету в хранилище
             {{ .Data.masking }}                                                     #имя свойства секрета, в котором хранится значение секрета
           {{- end -}}

        # подключение секрета настроек валидатора
        vault.hashicorp.com/secret-volume-path-validator-secret.yml: /deployments/config/validator # путь монтирования файла конфигурации валидатора из секрета
        vault.hashicorp.com/agent-inject-secret-validator-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, validator-secret.yml - имя секрета
        vault.hashicorp.com/agent-inject-file-validator-secret.yml: validator-secret.yml # имя файла конфигурации валидатора из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-validator-secret.yml: |    # шаблон извлечения секрета конфигурации валидатора
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ .Data.validator }}
           {{- end -}}


        # подключение секрета настроек ssl-соединения
        vault.hashicorp.com/secret-volume-path-ssl-config.yaml: /deployments/config/ssl # путь монтирования файла конфигурации SSL из секрета
        vault.hashicorp.com/agent-inject-secret-ssl-config.yaml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, ssl-config.yaml - имя секрета
        vault.hashicorp.com/agent-inject-file-ssl-config.yaml: ssl-secret.yml # имя файла конфигурации SSL из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-ssl-config.yaml: |    # шаблон извлечения секрета конфигурации SSL
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ .Data.config }}
           {{- end -}}

        # подключение секрета файла хранилища сертификатов для ssl-соединения
        vault.hashicorp.com/secret-volume-path-keystore.jks: /mnt/secrets # путь монтирования keystore из секрета (соответствует путю keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-secret-keystore.jks: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, keystore.jks - имя секрета
        vault.hashicorp.com/agent-inject-file-keystore.jks: keystore.jks # имя файла конфигурации keystore из секрета, будет создан по пути выше (соответствует имени файла keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-template-keystore.jks: |   # шаблон извлечения секрета keystore с декодированием из Base64
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ base64Decode .Data.keystore }}    #декодирование Base64 значения секрета
           {{- end -}}

Пример конфигурации Deployment для Go-Шлюза

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      labels
        secman-injector: enabled #обеспечивает подключение подов к SecMan

      annotations:
        # базовые аннотации
        vault.hashicorp.com/agent-init-first: "true" # смонтировать секреты до запуска основного контейнера
        vault.hashicorp.com/agent-inject: "true" # обеспечивает инъекцию init-контейнера и Sidecar Vault-Agent в поды Шлюза MQ
        vault.hashicorp.com/namespace: CIxxxxxxxx_CIxxxxxxxx #имя пространства в хранилище секретов формата CIxxxxxxxx_CIxxxxxxxx
        vault.hashicorp.com/agent-pre-populate: "true" #обеспечить наличие секретов до запуска основного контейнера
        vault.hashicorp.com/role: ciNNNNNNNN_xxx # роль в SecMan, получаемая при подключении к хранилищу (по тикету в Jira)
        vault.hashicorp.com/agent-volumes-default-mode: "0400" # задание прав на созданные файлы с секретами в примонтированном EmptyDir

        # подключение секрета настроек маскирования сообщений
        vault.hashicorp.com/secret-volume-path-masking-secret.yml: /deployments/config/masking # путь монтирования файла конфигурации маскирования из секрета
        vault.hashicorp.com/agent-inject-secret-masking-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, masking-secret.yml - имя секрета
        vault.hashicorp.com/agent-inject-file-masking-secret.yml: masking-secret.yml # имя файла конфигурации маскирования из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-masking-secret.yml: |    # шаблон извлечения секрета конфигурации маскирования
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}   #полный путь к секрету в хранилище
             {{ .Data.masking }}                                                     #имя свойства секрета, в котором хранится значение секрета
           {{- end -}}

        # подключение секрета настроек валидатора
        vault.hashicorp.com/secret-volume-path-validator-secret.yml: /deployments/config/validator # путь монтирования файла конфигурации валидатора из секрета
        vault.hashicorp.com/agent-inject-secret-validator-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, validator-secret.yml - имя секрета
        vault.hashicorp.com/agent-inject-file-validator-secret.yml: validator-secret.yml # имя файла конфигурации валидатора из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-validator-secret.yml: |    # шаблон извлечения секрета конфигурации валидатора
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ .Data.validator }}
           {{- end -}}


        # подключение секрета настроек ssl-соединения
        vault.hashicorp.com/secret-volume-path-ssl-config.yaml: /deployments/config/ssl # путь монтирования файла конфигурации SSL из секрета
        vault.hashicorp.com/agent-inject-secret-ssl-config.yaml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, ssl-config.yaml - имя секрета
        vault.hashicorp.com/agent-inject-file-ssl-config.yaml: ssl-secret.yml # имя файла конфигурации SSL из секрета, будет создан по пути выше
        vault.hashicorp.com/agent-inject-template-ssl-config.yaml: |    # шаблон извлечения секрета конфигурации SSL
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ .Data.config }}
           {{- end -}}

        # подключение секрета файла хранилища сертификатов для ssl-соединения
        vault.hashicorp.com/secret-volume-path-key.kdb: /mnt/secrets # путь монтирования key.kdb из секрета (соответствует пути keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-secret-key.kdb: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, key.kdb - имя секрета
        vault.hashicorp.com/agent-inject-file-key.kdb: key.kdb # имя файла key.kdb из секрета, будет создан по пути выше (соответствует имени файла keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-template-key.kdb: |   # шаблон извлечения секрета key.kdb с декодированием из Base64
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ base64Decode .Data.key_kdb }}    #декодирование Base64 значения секрета
           {{- end -}}

        # подключение секрета файла пароля хранилища сертификатов для ssl-соединения
        vault.hashicorp.com/secret-volume-path-key.sth: /mnt/secrets # путь монтирования key.sth из секрета (соответствует пути keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-secret-key.sth: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, key.sth - имя секрета
        vault.hashicorp.com/agent-inject-file-key.sth: key.sth # имя файла key.sth из секрета, будет создан по пути выше (соответствует имени файла keyStore/trustStore из ssl-secret.yml)
        vault.hashicorp.com/agent-inject-template-key.sth: |   # шаблон извлечения секрета key.sth с декодированием из Base64
           {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
             {{ base64Decode .Data.key_sth }}    #декодирование Base64 значения секрета
           {{- end -}}

С Istio

Для образов с поддержкой ожидания секретов SecMan:

  1. Добавить SE для SecMan. Также необходимо исправить параметр host в SE, в зависимости от стенда согласно таблице в инструкции

    SE-secman.yaml

    apiVersion: networking.istio.io/v1beta1
    kind: ServiceEntry
    metadata:
      name: se-secman
    spec:
      exportTo:
        - .
      hosts:
        - t.secrets.domainname.ru
      location: MESH_EXTERNAL
      ports:
        - name: https-443
          number: 443
          protocol: HTTPS
      resolution: DNS
    
  2. Добавить configMap со списком секретов, которые нужно ждать, и прокинуть в контейнер с приложением по пути /tmp/secman/config.

    configmap-secman-files.yaml

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: gateway-all-sp-secman
    data:
      secman.txt: |-
        /mnt/secrets/keystore.jks  #путь к файлу хранилища сертификатов для ssl-подключения. Должен быть указан так же в deployment (пример в секции подключение секрета файла хранилища сертификатов для ssl-соединения ниже)
        # далее - стандартные пути секретов конфигураций, неизменяемые
        /deployments/config/ssl/ssl-secret.yml
        /deployments/config/masking/masking-secret.yml
        /deployments/config/validator/validator-secret.yml
    

    Путь ``/tmp/secman/config`, по которому должен быть смонтирован файл с конфигурацией, зафиксирован в образе шлюза и не может быть изменен.

  3. В deployment Шлюза MQ добавляются аннотации и метки, обеспечивающие подключение агента SecMan и монтирование секретов из него. Пример файла с необходимыми аннотациями и монтированиями:

    Пример deployment

    kind: Deployment
    apiVersion: apps/v1
    spec:
      template:
        metadata:
          labels:
            secman-injector: enabled #обеспечивает подключение подов к SecMan
          annotations:
            # базовые аннотации
            sidecar.istio.io/inject: 'true'     #подключение Sidecar istio
            vault.hashicorp.com/agent-init-first: "false" # ОТКЛЮЧИТЬ (смонтировать секреты до запуска основного контейнера)
            vault.hashicorp.com/agent-inject: "true" # обеспечивает инъекцию init-контейнера и Sidecar Vault-Agent в поды Шлюза MQ
            vault.hashicorp.com/namespace: CIxxxxxxxx_CIxxxxxxxx #имя пространства в хранилище секретов формата CIxxxxxxxx_CIxxxxxxxx
            vault.hashicorp.com/agent-pre-populate: "false" #ОТКЛЮЧИТЬ (обеспечить наличие секретов до запуска основного контейнера_
            vault.hashicorp.com/role: ciNNNNNNNN_xxx # роль в SecMan, получаемая при подключении к хранилищу (по тикету в Jira)
            vault.hashicorp.com/agent-volumes-default-mode: "0400" # задание прав на созданные файлы с секретами в примонтированном EmptyDir
    
            # подключение секрета настроек маскирования сообщений
            vault.hashicorp.com/secret-volume-path-masking-secret.yml: /deployments/config/masking # путь монтирования файла конфигурации маскирования из секрета
            vault.hashicorp.com/agent-inject-secret-masking-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, masking-secret.yml - имя секрета
            vault.hashicorp.com/agent-inject-file-masking-secret.yml: masking-secret.yml # имя файла конфигурации маскирования из секрета, будет создан по пути выше
            vault.hashicorp.com/agent-inject-template-masking-secret.yml: |    # шаблон извлечения секрета конфигурации маскирования
            {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}   #полный путь к секрету в хранилище
              {{ .Data.masking }}                                                     #имя свойства секрета, в котором хранится значение секрета
            {{- end -}}
    
         # подключение секрета настроек валидатора
            vault.hashicorp.com/secret-volume-path-validator-secret.yml: /deployments/config/validator # путь монтирования файла конфигурации валидатора из секрета
            vault.hashicorp.com/agent-inject-secret-validator-secret.yml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, masking-secret.yml - имя секрета
            vault.hashicorp.com/agent-inject-file-validator-secret.yml: validator-secret.yml # имя файла конфигурации валидатора из секрета, будет создан по пути выше
            vault.hashicorp.com/agent-inject-template-validator-secret.yml: |    # шаблон извлечения секрета конфигурации валидатора
            {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
              {{ .Data.validator }}
            {{- end -}}
    
    
            # подключение секрета настроек ssl-соединения
            vault.hashicorp.com/secret-volume-path-ssl-config.yaml: /deployments/config/ssl # путь монтирования файла конфигурации SSL из секрета
            vault.hashicorp.com/agent-inject-secret-ssl-config.yaml: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, ssl-config.yaml - имя секрета
            vault.hashicorp.com/agent-inject-file-ssl-config.yaml: ssl-secret.yml # имя файла конфигурации SSL из секрета, будет создан по пути выше
            vault.hashicorp.com/agent-inject-template-ssl-config.yaml: |    # шаблон извлечения секрета конфигурации SSL
            {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
              {{ .Data.config }}
            {{- end -}}
    
            # подключение секрета файла хранилища сертификатов для ssl-соединения
            vault.hashicorp.com/secret-volume-path-keystore.jks: /mnt/secrets # путь монтирования keystore из секрета (соответствует путю keyStore/trustStore из ssl-secret.yml)
            vault.hashicorp.com/agent-inject-secret-keystore.jks: 'true' # признак монтирования секрета -vault.hashicorp.com/agent-inject-secret - статичная часть, keystore.jks - имя секрета
            vault.hashicorp.com/agent-inject-file-keystore.jks: keystore.jks # имя файла конфигурации keystore из секрета, будет создан по пути выше (соответствует имени файла keyStore/trustStore из ssl-secret.yml)
            vault.hashicorp.com/agent-inject-template-keystore.jks: |   # шаблон извлечения секрета keystore с декодированием из Base64
            {{- with secret "CIxxxxxxxx_CIxxxxxxxx/x/xxxx/xxx/x/KV/secret_name" -}}
              {{ base64Decode .Data.keystore }}    #декодирование Base64 значения секрета
            {{- end -}}
    
        spec:
          volumes:
            # подключение файла со списком путей к файлам, создание которых ожидается
            - name: secman-config
              configMap:
                name: gateway-all-sp-secman
                items:
                  - key: secman.txt
                    path: secman.txt
                defaultMode: 256
          containers:
              volumeMounts:
                - name: secman-config
                  readOnly: true
                  mountPath: /tmp/secman/config
    

    После старта Pod в логах образа Шлюза можно наблюдать счётчик времени ожидания, там же видно какие файлы ожидаются и какие из них найдены. Если он не останавливается длительное время, можно посмотреть в логи vault-agent - там должны быть следующие строки:

    логи агента secman

    [INFO] (runner) rendered "(dynamic)" => "/deployments/config/ssl/ssl-secret.yml"
    [INFO] (runner) rendered "(dynamic)" => "/deployments/config/masking/masking-secret.yml"
    [INFO] (runner) rendered "(dynamic)" => "/mnt/secrets/keystore.jks"
    [INFO] (runner) rendered "(dynamic)" => "/deployments/config/validator/validator-secret.yml"
    

    Если их нет - в конфигурации допущена ошибка.

Для старых образов, в которых нет поддержки SecMan

Для образов, в которых нет скрипта ожидания монтирования секретов, есть обходной путь (только для DEV-окружения):

Дополнительно к действиям выше необходимо:

  1. Убрать аннотацию traffic.sidecar.istio.io/excludeOutboundPorts если присутствует

  2. Добавить скрипты ожидания секретов и запуска приложения шлюза

    all-sp-entrypoint.yaml

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: gateway-all-sp-secman-wait
    data:
      entrypoint.sh: |-
        #!/bin/sh
        java -Dfile.encoding=UTF-8 -Dspring.jmx.enabled=false
        -Duser.timezone=Europe/Moscow -XX:+ExitOnOutOfMemoryError
        -Dcom.ibm.mq.cfg.useIBMCipherMappings=false
        -Dspring.config.location=classpath:application-default.yml,file:/deployments/config/common/common-config.yml,file:/deployments/config/application.yml,file:/deployments/config/environment/environment.yml,file:/deployments/config/ssl/ssl-secret.yml,file:/deployments/config/masking/masking-secret.yml,file:/deployments/config/validator/validator-secret.yml
        -Djava.security.egd=file:/dev/./urandom -cp
        ./app/resources:./app/classes:./app/libs/*
        com.sbt.synapse.mq.MqGatewayApplication
      waitingSecrets.sh: |-
        #!/bin/sh
        config_file=/tmp/secman/config/secman.txt
        checked_files=`cat $config_file`
        files_count=0
        for file in $checked_files ; do
            files_count=$(( files_count + 1 ))
        done
        exists_files_count=0
        time_counter=0
        while [ $exists_files_count != $files_count ]; do
            exists_files_count=0
            for file in $checked_files ; do
                if [ -f $file ]; then
                    exists_files_count=$(( exists_files_count + 1 ))
                fi
            done
            sleep 1
            time_counter=$(( time_counter + 1 ))
            echo "Waiting $time_counter s."
        done
    

    Для Go-шлюза entrypoint выглядит так:

    all-sp-entrypoint.yaml

    kind: ConfigMap
    apiVersion: v1
    metadata:
      name: gateway-all-sp-secman-wait
    data:
      entrypoint.sh: |-
        #!/bin/sh
        /go/src/app/bin/go-mq-gateway -c "/deployments/config/common/common-config.yml,/deployments/config/application.yml,/deployments/config/environment/environment.yml,/deployments/config/ssl/ssl-secret.yml,/deployments/config/masking/masking-secret.yml,/deployments/config/validator/validator-secret.yml"
      waitingSecrets.sh: |-
        #!/bin/sh
        config_file=/tmp/secman/config/secman.txt
        checked_files=`cat $config_file`
        files_count=0
        for file in $checked_files ; do
            files_count=$(( files_count + 1 ))
        done
        exists_files_count=0
        time_counter=0
        while [ $exists_files_count != $files_count ]; do
            exists_files_count=0
            for file in $checked_files ; do
                if [ -f $file ]; then
                    exists_files_count=$(( exists_files_count + 1 ))
                 fi
            done
         sleep 1
         time_counter=$(( time_counter + 1 ))
         echo "Waiting $time_counter s."
        done
    
  3. Добавить следующие поля в описание контейнера:

    entrypoint

    kind: Deployment
    apiVersion: apps/v1
    spec:
      template:
        spec:
          containers:
             # ниже - команда и аргументы вызова скриптов ожидания секретов и запуска шлюза
            command:
              - /bin/sh
            args:
              - '-c'
              - /tmp/secman/waitingSecrets.sh && /tmp/secman/entrypoint.sh
    
  4. Добавить в deployment монтирование файлов и команду запуска (разрешить исполнение):

    Пример deployment

    kind: Deployment
    apiVersion: apps/v1
    spec:
      template:
        spec:
          volumes:
            - name: secman-config
              configMap:
                name: gateway-all-sp-secman
                items:
                  - key: secman.txt
                    path: secman.txt
                defaultMode: 256
            - name: secman-waiting
              configMap:
                name: gateway-all-sp-secman-wait
                items:
                  - key: waitingSecrets.sh. # скрипт ожидания секретов
                    path: waitingSecrets.sh
                  - key: entrypoint.sh # скрипт запуска шлюза
                    path: entrypoint.sh
                defaultMode: 493   # разрешение на исполнение скриптов
          containers:
              volumeMounts:
                - name: secman-config
                  readOnly: true
                  mountPath: /tmp/secman/config
                - name: secman-waiting
                  readOnly: true
                  mountPath: /tmp/secman/
            # ниже - команда и аргументы вызова скриптов ожидания секретов и запуска шлюза
              command:
                - /bin/sh
              args:
                - '-c'
                - /tmp/secman/waitingSecrets.sh && /tmp/secman/entrypoint.sh
    

Динамическая загрузка TLS сертификатов#

При изменении файлов секретов с хранилищами ключей внешней системой хранения сертификатов, Шлюз MQ должен перечитать новые значения и использовать их для создания подключений.

В реализации на Java#

Шлюз MQ отслеживает изменение файлов с хранилищами ключей. Новые значения применяются при создании новых подключений. При работе с менеджерами IBM MQ в конфигурацию Шлюза добавлены дополнительные настройки:

mq:
  connection:
    certificateReloadEnabled: true
    certificateReloadDelay: 2s

При работе с провайдерами MQ отличными от IBM эти настройки игнорируются

В реализации Шлюза MQ на Golang#

Последовательность действий при обновлении сертификатов:

  1. Шлюз MQ мониторит изменения (обновление, удаление, создание) файлов хранилищ сертификатов (расположение файлов указывается в конфигурации).

  2. При наступлении события изменения файлов, Шлюз MQ:

    • выдерживает паузу определяемую алгоритмом временного смещения для снижения коллизий (перекрытия по времени недоступности двух или более pods общего Deployment (см. ниже описание алгоритма));

    • останавливает обработку сообщений;

    • останавливает все коннекты к менеджерам MQ;

    • перечитывает файлы хранилищ сертификатов;

    • восстанавливает коннекты с использованием новых сертификатов;

    • возобновляет обработку сообщений.

  3. Сообщения принятые по каналам HTTP и gRPC во время обновления сертификатов (с момента остановки обработки до ее возобновления) накапливаются в пуле потоков обработки, и обрабатываются после восстановления коннектов.

  4. Если пул потоков обработки исчерпан, а процедура обновления еще не завершена, новые входящие вызовы по каналам HTTP и gRPC будут отклоняться шлюзом, и средствами istio будет выполняться повтор вызова на другие pod-ы шлюза.

  5. Предусмотрена настройка, позволяющая задать состояние Readiness пробы на время проведения процедуры обновления. По умолчанию Readiness - проба не опускается. Снятие Readiness-пробы выводит шлюз из балансировки на время приостановки обработки. Это позволяет предотвратить повторы вызовов на уровне сервис-меша и связанное с этим увеличение латентности в прикладных интеграциях.

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

С этой целью Шлюз сдвигает начало применения новых сертификатов от момента изменения файлов хранилищ. Время сдвига определяется для каждого экземпляра независимо, по вероятностному алгоритму, который позволяет минимизировать количество коллизий перезапуска коннектов, но не исключает их полностью.

Описание алгоритма#

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

Временное смещение определяется исходя из номера сегмента для конкретного пода и размера интервала обновления.

Интервал обновления — это время необходимое одному поду шлюза, для выполнения процедуры применения сертификатов

Произведение интервала обновления и количества сегментов дает общее время выполнения процедуры обновления сертификатов на deployment шлюза.

При уменьшении фактора репликации вероятность коллизий будет снижаться, при этом величина коллизий (количество подов одновременно начавших процедуру применения сертификатов) и общее время выполнения процедуры на deployment сохраняются.

Шлюз вносит дополнительную фиксированную временную задержку, задаваемую параметром mq.sslCertUpdate.certificateReloadDelay для того, чтобы при несинхронном изменении файлов ключей и сертификатов со стороны внешней системы хранения сертификатов исключить излишние повторы процедуры обновления.

Конфигурирование#

Для настройки алгоритма используются следующие параметры (mq.sslCertUpdate):

enable - включение(true)/выключение(false) механизма обновления сертификатов. Если установлено значение false, контроль за изменением файлов хранилища сертификатов отключается.

checkInterval - устанавливает периодичность проверки изменения файлов хранилища сертификатов.

downReadinessProbe - задает нужно (true), или не нужно (false) опускать Readiness-пробу на время выполнения обновления сертификатов. Если значение true, то Readiness-проба на каждом конкретном экземпляре шлюза будет переводится в состояние down с момента остановки обработки сообщений до момента ее возобновления этим конкретным экземпляром.

intervalsNumber - количество интервалов (сегментов распределения). С его уменьшением вероятность коллизий будет увеличиваться. Можно уменьшить, если у deployment малый фактор репликации (количество запущенных подов < 5), чтобы уменьшить обще время выполнения процедуры замены сертификатов.

updateTimeInSeconds - Интервал обновления — время применения сертификатов для одного пода Шлюза. Нужно увеличивать, если у шлюза задано большое количество коннектов (очередей и брокеров), так как в этом случае отключение и подключение всех коннектов потребует больше времени. Пропорционально увеличивает общее время выполнения для deployment.

roundTimeInMinutes - интервал на который округляется (в большую сторону) момент изменения файлов хранилищ ключей. Это позволяет нивелировать временную разницу распространения обновленных файлов по нодам, и обеспечить независимые поды общей точкой отсчета для начала процедуры. Нужно увеличивать, если в проекте наблюдается значительный разброс времени распространения файлов по нодам. Приводит к сдвигу общего времени выполнения процедуры для deployment на величину roundTimeInMinutes.

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

Пример настройки (приведены значения по умолчанию):

mq:
  sslCertUpdate:
    enable: false
    checkInterval: 30000ms
    downReadinessProbe: false
    intervalsNumber: 20
    updateTimeInSeconds: 2
    roundTimeInMinutes: 10
    certificateReloadDelay: 30s
Обработка внештатных ситуаций#

В процессе обновления сертификатов возможно возникновение исключительных ситуаций:

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

  2. Новый файл хранилища поврежден (файл есть, но "битый"). При попытке загрузить новые сертификаты возникнет ошибка инициализации библиотек криптографии. Будет снята Readiness - проба, шлюз будет пытаться инициализировать библиотеку. После устранения ошибки будут загружены сертификаты, и восстановлена Readiness-проба.

  3. Сертификаты в новом файле хранилища не валидные. После обновления сертификатов возникнет ошибка установки соединения с менеджерами MQ. Readiness-проба будет снята. Шлюз будет пытаться установить соединение с менеджерами. После устранения ошибки подключения будут восстановлены, и Readiness-проба поднята.

Особенности использования реализации Шлюза MQ на Golang#

Deployment#

До релиза 3.2 в deployment Шлюза MQ при использовании реализации на Golang должны были добавляться точки монтирования /IBM и //.mqm на тома типа emptyDir.

Пример:

      volumes:
        - name: ibm
          emptyDir: {}
        - name: mqm
          emptyDir: {}
...
          volumeMounts:
            - name: ibm
              mountPath: /IBM
            - name: mqm
              mountPath: //.mqm

С релиза 3.2 эта необходимость исключена! Папки перенесены внутрь образа шлюза, права выданы только на чтение, т. к. требование безопасности установить параметр readOnlyRootFileSystem = true все равно заблокирует запись в них. Вследствие этого файлы журналов клиента MQ создаваться не будут. Если эти журналы нужны, то нужно переадресовать их в каталог на который есть права на запись (например /opt/synapse/logs). Сделать это можно, добавив в Deployment Шлюза переменную окружения MQ_OVERRIDE_DATA_PATH.

Пример:

          env:
            - name: MQ_OVERRIDE_DATA_PATH
              value: /opt/synapse/logs

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

Пример:

          args:
            - '-l'
            - debug
            - '-c'
            - /deployments/config/application.yml,/deployments/config/ssl/ssl-secret-go.yml

Важно Уровень логирования по требованию безопасности должен быть выставлен в info (или выше).

Важно В средах k8s или DAPP может измениться расположение HOME каталога, поэтому в этих средах требуется явно задать привязку домашнего каталога приложения к корневому через деплоймент Шлюза.

Пример:

          env:
            - name: HOME
              value: /

Хранилище сертификатов#

Шлюз MQ в реализации на Golang использует хранилище сертификатов в формате CMS (.kdb). Может быть получено из хранилища ключей в формате JKS путем конвертации.

Для конвертации выполнить команду:

runmqckm -keydb -convert -db <имя файла хранилища>.jks -new_format cms -pw <пароль на хранилище> -stash

(В среде Windows надо вызывать runmqckm.exe)

В каталоге будут созданы три файла с расширениями .kdb, .rdb, .sth. Файл .rdb не нужен для работы Шлюза MQ.

Настройки SSL#

Важно! Go-шлюз использует коды схем шифрования IBM (как на MQ-менеджере), которые в общем случае не совпадают с кодами Java. Поэтому при переходе надо проверять, и, при необходимости, корректировать название схемы шифрования (параметр sslCipherSuite)

Настройки SSL в Файле ssl-secret.yml в основном совпадают с настройками для реализации на java. Изменяется имя хранилища на полученное после конвертации в формат CMS, лишние параметры нужно убрать в соответствии с примером.

Пример:

mq:
  sslConfigs:
    - sslConfigName: <Имя профиля> # Связывает профиль настроек TLS с настройками соединения по одноименному параметру. Если не задано, используется 'default' 
      sslCipherSuite: ECDHE_RSA_AES_128_GCM_SHA256
      sslFipsRequired: false
      sslResetCount: 0
      sslSocketFactory:
        protocol: TLSv1.2
        keyStore: /mnt/secrets/<имя хранилища>.kdb          # было: /mnt/secrets/<имя хранилища>.jks меняем на .kdb
        # keyStorePassword: <пароль на хранилище>           убираем
        # keyStoreType: JKS                                 убираем
        # trustStore: /mnt/secrets/<имя хранилища>.jks      убираем
        # trustStorePassword: <пароль на хранилище>         убираем
        # trustStoreType: JKS                               убираем
        sslCertAlias: ibmwebspheremqsnp99usr

Конфигурация#

В общем случае Шлюз MQ в реализации на Golang более критичен к формату параметров в файле конфигурации.

Параметр port в секции mq должен иметь значение в формате целого числа:

Пример:

mq:
  connection:
    connections:
    - channel: <Имя канала>
      queueManager: <Имя менеджера>
      hostname: <Имя хоста>
      port: 3012
      sslConfigName: <Имя профиля> # задает имя профиля настроек TLS для данного соединения. Если не задано, будет использован профиль с именем 'default'

в формате json:

    mq:
      connection:
        connections: [{'channel': '<Имя канала>', 'queueManager': '<Имя менеджера>', 'hostname': '<Имя хоста>', 'port': 3012}]

Для элементов типа list значение всегда должно иметь формат списка, даже если в списке всего один элемент. Например, для входящей очереди:

# либо:
        receiveQueue:
          - SYNAPSE.ASAP.IN
# либо:
        receiveQueue: [SYNAPSE.ASAP.IN]

Название параметра должно быть задано точно так, как указано в документации. Например, параметр syncReceiver должен быть задан в camel case, а параметры mq-http и http-mq - через дефис.

Шлюз MQ в реализации на Golang всегда работает в совмещенном sync/async режиме (workMode: all), поэтому названия очередей должны задаваться в соответствующих параметрах:

Для шлюза потребителя:

mq:
  systemType: 
  workMode: all
  connection:
    receiveQueue:
      - <Очередь для асинхронного режима> # задается при переводе шлюзов async или all(sync/async)
    receiveQueueSync:
      - <Очередь для синхронного режима> # задается при переводе шлюзов sync или all (sync/async)

Для шлюза поставщика:

mq:
  systemType: sp
  workMode: all
  connection:
    receiveQueue:
      - <Очередь для асинхронного режима> # задается при переводе шлюзов async
  syncReceiver:
    defaultReceiveQueue: <Очередь для синхронного режима> # задается при переводе шлюзов sync (если есть необходимость задать дефолтную очередь для ответов)

Синтаксис, используемый в выражениях Expression в секции routing для реализации на Golang отличается от реализации на Java.

Основное отличие:

Поскольку строка в goel - это массив символов, для обработки нужно использовать функции и/или операторы, а не методы.

Реализация на java

Реализация на Golang

Комментарий

value.toLowerCase()

toLower(value)

Кастомное расширение

value.toUpperCase()

toUpper(value)

Кастомное расширение

"<строка>".equals(value)

'<строка>'==value

serviceName.length()

len(ServiceName)

serviceName.substring(0, serviceName.length() - 2)

ServiceName[0:len(ServiceName)-2]

Для выделения подстроки используется slice

value.contains('<строка>')

value contains '<строка>'

value.indexOf('<строка>')

indexOf(value,'<строка>')

value.replace('-','')

replace(value,'-','',<количество>)

value.split('-')[1]

split(value,'-')[1]

'TB' + (new Integer(variables['Id1']) + 1)

'TB'+intToStr(strToInt(variables['Id1'])+1)

new Integer(variables['Id1'])

strToInt(variables['Id1'])

(new Integer(variables['Id1']) + 1)

inc(strToInt(variables['Id1']),1)

(new Integer(variables['Id1']) - 1)

dec(strToInt(variables['Id1']),1)

variables['Id1']?:variables['Id2']?:'0'

coalesce(variables['Id1'],variables['Id2'],'0');

Реализация на Go поддерживает тернарный оператор.

Выражения:

SCName+'-'+ServiceName[0:len(ServiceName)-2]+'-'+SPName

"variables['SCName'] + '-' + 'srv' + variables['ServiceName'][0:len(ServiceName)-2] + '-' + variables['SPName']"

destinationExpression: '"ufsfl-srvgetcarddeliverystatus-oddo-rq"'

destinationExpression: "'ufsfl-srvgetcarddeliverystatus-oddo-rq'"

destinationExpression: ('ufsfl-srvgetcarddeliverystatus-oddo-rq')

"'<строка>' == toLower(value) ? '<строка 1>':value"

будут работать в реализации на Golang.

Важно! В реализации Шлюза MQ на Golang для ExtractionRule отсутствуют типы (type): fromGrpcHeader, fromAuthority.

Важно! В реализации Шлюза MQ на Java список routing.routeList[].destinations - регистрозависимый, а в реализации Шлюза MQ на Golang - регистронезависимый.

Шлюз MQ в реализации на Golang с релиза 2.11 при считывании сообщений из очереди, преобразует значения пользовательских заголовков сообщения в строки с учетом их исходного типа. В реализации на Java это было заложено изначально.

Размер считываемого из MQ сообщения в реализации на Golang по умолчанию ограничен 100 Кб. Необходимо явно прописать ограничение размера сообщений в настройке Шлюза MQ если оно превышает 100 кБ.

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

За ограничение размера сообщения в реализациях на Java и Golang отвечают разные настройки:

#Java

mq:
  rejectMaxMessageSize: 350KB

#Go

mq:
  maxMsgLength: 350

В реализации на Golang по умолчанию установлено ограничение на размер сообщения обрабатываемого gRPC сервером 4 MB. За ограничение размера сообщения в реализациях на Java и Golang отвечают разные настройки:

#Java

grpc:
  maxMessageSizeInMb: 2MB

#Go

grpc:
  server:
    maxMessageSizeInMb: 2MB

Механизм чистки .FDC файлов#

Реализация Шлюза MQ на Golang использует в своем составе библиотеки распространяемого (redistributable) клиента IBM MQ. При его работе, в случае возникновения ошибок в каталоге /IBM/MQ/data/errors создаются файлы дампов (.FDC) большого размера. При накоплении .FDC файлов, Pod шлюза может выселяться (evict) с ноды из-за нехватки ресурсов диска. Для экономии дискового пространства предусмотрен механизм очистки .FDC файлов.

Механизм удаляет .FDC файлы при превышении лимита занятого места в директории начиная со старых. Лимит задается настройкой maxFileSizes. Период сканирования задается настройкой period. Шлюз сканирует каталог и определяет размер всех файлов .FDC в нем, а так же их возраст. Если размер файлов больше значения maxFileSizes, Шлюз начинает удалять файлы начиная со старых, пока не будет очищено место в объеме большем или равном заданному настройкой freePercent (в процентах от maxFileSizes).

Дополнительно Шлюз раз в сутки, в момент времени, заданный настройкой outdatedScanTime проверяет возраст файлов .FDC и удаляет все файлы старше, чем задано настройкой maxAgeInDays.

Пример настроек:

cleanFDC:
  enable: false # включение механизма очистки, по умолчанию - выключен
  path: "/IBM/MQ/data/errors/" # Путь по которому искать .FDC файлы. Настройка не влияет на то где файлы формируются.
  maxFilesSize: 50MB # Суммарный размер .FDC файлов, при превышении которого включается очистка.
  freePercent: 80 # Сколько процентов от суммарного размера должно быть освобождено при очистке
  period: "60s" # Период сканирования
  outdatedScanTime: "01:00" # Время запуска удаления устаревших файлов
  maxAgeInDays: 7 # возраст в днях при достижении которого файлы считаются устаревшими
  retry: # Настройка количества повторных попыток, если при попытки удаления файл был заблокирован.
    attempts: 3
    waitDuration: 500ms

Пропуск XML-декларации при разборе тела XML-сообщений#

Канонический формат XML-сообщения предполагает наличие в начале XML-декларации (строка вида <?xml version="1.0" encoding="UTF-8"?>).

Параметр encoding должен соответствовать кодировке символов тела сообщения. В противном случае возможны ошибки при разборе тела сообщения содержащего символы национального алфавита.

Для исключения таких ошибок добавлена возможность пропуска XML-декларации при разборе тела сообщения. Возможность включается установкой параметра parser.xml.skipDeclaration в конфигурации шлюза в true, при этом декларация при разборе сообщения будет проигнорирована.

Реализация Шлюза MQ на Java всегда игнорирует XML-декларацию в теле сообщения.

Особенности используемого базового образа#

Базовый образ реализации шлюза MQ на Java#

Базовый образ реализации Шлюза MQ на Java собран на основе образа ubi-minimal от RedHat.

Для правильной работы приложения в образ установлена JVM: Eclipse OpenJ9 VM AdoptOpenJDK. Требуемая версия указана в разделе «Системные требования» документа «Руководство по установке»

Базовый образ реализации Шлюза MQ на Golang#

Базовый образ реализации Шлюза MQ на Golang собран на основе образа ubi-minimal от RedHat.

Для правильной работы приложения в образе, в операционной системе, включена русская локализация (ru_RU.UTF-8) и установлен распространяемый клиент IBM MQ. Требуемая версия указана в разделе «Системные требования» документа «Руководство по установке»

Настройка регистрации событий аудита#

Шлюз MQ позволяет фиксировать события аудита в локальном буфере аудита.

Перечень фиксируемых событий Аудита, которые возникают в процессе функционирования Шлюза MQ:

Код

Событие

Комментарий

MQ_success

Успешное подключение к менеджеру MQ

Фиксируется при каждом успешном подключении Шлюза MQ к менеджеру MQ. Для многоточечного подключения (один Шлюз к нескольким менеджерам) фиксируется отдельное событие подключения к каждому менеджеру MQ.

MQ_error

Ошибка подключения к менеджеру MQ

Фиксируется при ошибке подключения Шлюза MQ к менеджеру MQ. Для многоточечного подключения события фиксируются для каждого подключенного менеджера отдельно.

Параметры события

Общие:

Код

Параметр

Комментарий

Время события

Время фиксируется по моменту возникновения события в конкретном экземпляре шлюза

Проект OpenShift

Наименование проекта в OpenShift с запущенным Pod шлюза, на котором произошло событие

Источник события

Имя Pod шлюза, на котором произошло событие

Имя Node

Имя Node, на которой запущен Pod шлюза

IP адрес Node

IP Node, на которой запущен Pod шлюза

Имя менеджера

Имя менеджера, к которому выполнялось подключение

Канал

Имя канала через, который выполнялось подключение

Имя хоста

Имя хоста менеджера, к которому выполнялось подключение

Номер порта

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

Схема шифрования

Схема шифрования указанная в настройках Pod шлюза

DN шлюза

DN сертификата шлюза из настроек

DN менеджера MQ

Ожидаемый DN менеджера MQ из настройки sslPeerName конфигурации шлюза.

Описание ошибки

Для успешного подключения: пусто;
Для ошибки подключения: фрагмент stack trace шлюза, описывающий ошибку, например:
nested exception is com.ibm.mq.MQException: JMSCMQ0001: Не удалось выполнить вызов IBM MQ; код завершения '2' ('MQCC_FAILED'), причина '2398' ('MQRC_SSL_PEER_NAME_MISMATCH') com.ibm.mq.jmqi.JmqiException: CC=2;RC=2398;AMQ9204: Запрос на подключение к хосту 'hostname.domainname.ru(1452)' отклонен. [1=com.ibm.mq.jmqi.JmqiException[CC=2;RC=2398;AMQ9636: Отличительное имя SSL не соответствует имени узла; канал '?'. [4=CN=SYNAPSE]],3=hostname.domainname.ru(1452),5=RemoteTCPConnection.protocolConnect]

Чтобы отправить событие аудита в ТС Аудит (AUDT) Platform V Audit SE, в нем предварительно должна быть зарегистрирована метамодель события.

Шлюз формирует метамодель в формате JSON:

{
    "metamodelVersion": "1",
    "module": "mq-gateway",
    "events": [
        {
            "name": "MQ_success",
            "description": "Успешное подключение к менеджеру MQ",
            "success": "true",
            "mode": "reliability",
            "params": [
                {
                    "name": "time",
                    "description": "Время события (timestamp)"
                },
                {
                    "name": "os_project",
                    "description": "Проект OpenShift"
                },
                {
                    "name": "event_source",
                    "description": "Имя Pod источника события"
                },
                {
                    "name": "node_name",
                    "description": "Имя Node для Pod источника события"
                },
                {
                    "name": "node_ip",
                    "description": "IP Node для Pod источника события"
                },
                {
                    "name": "mqm_name",
                    "description": "Имя менеджера к которому выполнялось подключение"
                },
                {
                    "name": "mqm_channel",
                    "description": "Имя канала по которому выполнялось подключение"
                },
                {
                    "name": "mqm_host",
                    "description": "Имя хоста менеджера к которому выполнялось подключение"
                },
                {
                    "name": "mqm_port",
                    "description": "Номер порта менеджера к которому выполнялось подключение"
                },
                {
                    "name": "cipher",
                    "description": "Используемая схема шифрования"
                },
                {
                    "name": "dn_mqgateway",
                    "description": "DN сертификата шлюза из настроек"
                },
                {
                    "name": "dn_mqm",
                    "description": "Ожидаемый DN менеджера MQ из настроек"
                },
                {
                    "name": "error_desc",
                    "description": "Описание ошибки"
                }
            ]
        },
        {
            "name": "MQ_error",
            "description": "Ошибка подключения к менеджеру MQ",
            "success": "false",
            "mode": "reliability",
            "params": [
                {
                    "name": "time",
                    "description": "Время события (timestamp)"
                },
                {
                    "name": "os_project",
                    "description": "Проект OpenShift"
                },
                {
                    "name": "event_source",
                    "description": "Имя Pod источника события"
                },
                {
                    "name": "node_name",
                    "description": "Имя Node для Pod источника события"
                },
                {
                    "name": "node_ip",
                    "description": "IP Node для Pod источника события"
                },
                {
                    "name": "mqm_name",
                    "description": "Имя менеджера к которому выполнялось подключение"
                },
                {
                    "name": "mqm_channel",
                    "description": "Имя канала по которому выполнялось подключение"
                },
                {
                    "name": "mqm_host",
                    "description": "Имя хоста менеджера к которому выполнялось подключение"
                },
                {
                    "name": "mqm_port",
                    "description": "Номер порта менеджера к которому выполнялось подключение"
                },
                {
                    "name": "cipher",
                    "description": "Используемая схема шифрования"
                },
                {
                    "name": "dn_mqgateway",
                    "description": "DN сертификата шлюза из настроек"
                },
                {
                    "name": "dn_mqm",
                    "description": "Ожидаемый DN менеджера MQ из настроек"
                },
                {
                    "name": "error_desc",
                    "description": "Описание ошибки"
                }
            ]
        }
    ]
}

Метамодель отправляется один раз при старте приложения.

Примеры событий в JSON - формате:

а) Успешное

{
    "module": "mq-gateway",
    "metamodelVersion": "1",
    "name": "MQ_success",
    "userNode": "NO-USERNODE",
    "userLogin": "NO-USER",
    "createdAt": "1234567890000",
    "session": "NO-SESSION",
    "userName": "",
    "tags": [
        "<project_name>",
        "<deployment_name>"
    ],
    "params": [
        {
            "name": "time",
            "value": "1234567890"
        },
        {
            "name": "os_project",
            "value": "ci01994970-idevgen2-synapse-esbub-dev"
        },
        {
            "name": "event_source",
            "value": "test-gw-56558d77b6-hcl9v"
        },
        {
            "name": "node_name",
            "value": "nodename.domainname.ru"
        },
        {
            "name": "node_ip",
            "value": "192.0.0.1"
        },
        {
            "name": "mqm_name",
            "value": "SYNAPSE.DEV.MGR"
        },
        {
            "name": "mqm_channel",
            "value": "SRV.SSL.CHANNEL"
        },
        {
            "name": "mqm_host",
            "value": "hostname.domainname.ru"
        },
        {
            "name": "mqm_port",
            "value": "1452"
        },
        {
            "name": "cipher",
            "value": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
        },
        {
            "name": "dn_mqgateway",
            "value": "CN=TEST-GW"
        },
        {
            "name": "dn_mqm",
            "value": "CN=SYNAPSE"
        },
        {
            "name": "error_desc",
            "value": ""
        }
    ]
}

Ошибка

{
    "module": "mq-gateway",
    "metamodelVersion": "1",
    "name": "MQ_error",
    "userNode": "NO-USERNODE",
    "userLogin": "NO-USER",
    "createdAt": "1234567890000",
    "session": "NO-SESSION",
    "userName": "",
    "tags": [
        "<project_name>",
        "<deployment_name>"
    ],
    "params": [
        {
            "name": "time",
            "value": "1234567890"
        },
        {
            "name": "os_project",
            "value": "ci01994970-idevgen2-synapse-esbub-dev"
        },
        {
            "name": "event_source",
            "value": "test-gw-56558d77b6-hcl9v"
        },
        {
            "name": "node_name",
            "value": "nodename.domainname.ru"
        },
        {
            "name": "node_ip",
            "value": "192.0.0.1"
        },

        {
            "name": "mqm_name",
            "value": "SYNAPSE.DEV.MGR"
        },
        {
            "name": "mqm_channel",
            "value": "SRV.SSL.CHANNEL"
        },
        {
            "name": "mqm_host",
            "value": "hostname.domainname.ru"
        },
        {
            "name": "mqm_port",
            "value": "1452"
        },
        {
            "name": "cipher",
            "value": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
        },
        {
            "name": "dn_mqgateway",
            "value": "CN=TEST-GW"
        },
        {
            "name": "dn_mqm",
            "value": "CN=SYNAPSE"
        },
        {
            "name": "error_desc",
            "value": "nested exception is com.ibm.mq.MQException: JMSCMQ0001: Не удалось выполнить вызов IBM MQ; код завершения '2' ('MQCC_FAILED'), причина '2398' ('MQRC_SSL_PEER_NAME_MISMATCH')
com.ibm.mq.jmqi.JmqiException: CC=2;RC=2398;AMQ9204: Запрос на подключение к хосту 'hostname.domainname.ru(1452)' отклонен. [1=com.ibm.mq.jmqi.JmqiException[CC=2;RC=2398;AMQ9636: Отличительное имя SSL не соответствует имени узла; канал '?'. [4=CN=SYNAPSE]],3=hostname.domainname.ru(1452),5=RemoteTCPConnection.protocolConnect]"
        }
    ]
}

Получение данных об имени проекта, имени Node и ее IP-адреса.

Чтобы приложение Шлюза получило доступ к этим данным, в deployment Шлюза должны быть добавлены следующие настройки:

env:
  - name: PROJECT_NAME
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace
  - name: NODE_NAME
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: spec.nodeName
  - name: NODE_IP
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: status.hostIP

Шлюз при заполнении данных о событиях должен передать в параметре os_project значение переменной окружения PROJECT_NAME, в параметре node_name значение переменной окружения NODE_NAME, в параметре node_ip значение переменной окружения NODE_IP.

Для включения регистрации событий, аудита нужно в конфигурацию Шлюза MQ добавить секцию:

audit:
  enable: true
  transportType: file
  directory: /opt/synapse/logs

В этом случае Шлюз MQ при старте создает в каталоге, указанном в параметре directory два файла:

event.aud

metamodel.amm

В файл metamodel.amm однократно выгружается метамодель событий. В файл event.aud начинают выгружаться события аудита по мере их возникновения на Шлюзе.

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

Для прямой отправки событий в ТС Аудит, секцию audit нужно настроить следующим образом.

audit:
  enable: true
  transportType: http
  http:
    client:
      httpMethod: POST
      url: http://хост:порт
    eventPath: /event_endpoint
    metamodelPath: /metamodel_endpoint
    responseTimeout: 10000
    maxBufferSize: 3000
    retry:
      attempts: 10

При старте шлюз подключится к endpoint ТС Аудит зарегистрирует метамодель и начнет отправку событий.

Одна и та же версия метамодели может регистрироваться произвольное количество раз, но при этом описатель метамодели не должен меняться. Поэтому версия метамодели вместе с самой метамоделью прошита в коде шлюза и меняется только при перепоставке.

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

При возникновении ошибок отправки, шлюз сохраняет событие в локальном буфере в памяти приложения и пытается отправить его повторно. Количество попыток настраивается в параметре audit.http.retry.attempt. Количество событий, которое может быть сохранено в буфере настраивается в параметре audit.http.maxBufferSize.

Если возникает ошибка при регистрации модели, то шлюз повторяет попытки, пока модель не будет зарегистрирована, при этом readiness проба не поднимается, и перевод трафика на этот экземпляр шлюза не происходит.

Особенности настройки SSL к менеджерам MQ в реализации Шлюза MQ на Java#

В Шлюзе MQ настройки SSL заданы в секции mq.sslConfigs[]. В этой секции содержатся как параметры настройки SSL, так и пароли на хранилища ключей, например:

mq:
  sslConfigs:
    - sslConfigName: default
      sslCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
      sslFipsRequired: false
      sslResetCount: 0
      sslSocketFactory:
        protocol: TLSv1.2
        keyStore: /mnt/secrets/key.jks
        keyStorePassword: <пароль на keystore>
        keyStoreType: jks
        trustStore: /mnt/secrets/key.jks
        trustStorePassword: <пароль на truststore>
        trustStoreType: jks
        sslCertAlias: test-gw
    - sslConfigName: Name1
      sslCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
      sslFipsRequired: false
      sslResetCount: 0
      sslSocketFactory:
        protocol: TLSv1.2
        keyStore: /mnt/secrets/key.jks
        keyStorePassword: <пароль на keystore>
        keyStoreType: jks
        trustStore: /mnt/secrets/key.jks
        trustStorePassword: <пароль на truststore>
        trustStoreType: jks
        sslCertAlias: test-gw

Чтобы пароли можно было хранить отдельно от основной конфигурации в настройки добавлен блок sslConfigData, который содержит секции с параметрами keyStorePassword, trustStorePassword, которые можно заполнить значениями паролей, и который можно разместить в отдельном файле ssl-secret.yml в артефакте типа Secret. Имена секций должны совпадать с именами профилей настроек SSL (sslConfigName) из sslConfigs[]. Например:

sslConfigData:
  default:
    keyStorePassword: <пароль на keystore>
    trustStorePassword: <пароль на truststore>
  Name1:
    keyStorePassword: <пароль на keystore>
    trustStorePassword: <пароль на truststore>

Шлюз MQ при загрузке считывает значения паролей и сопоставляет их с соответствующими им хранилищами ключей по совпадению имени секции из блока sslConfigData с именем профиля настройки SSL из sslConfigs[]. Если в профиле настроек SSL в блоке mq.sslConfigs имя профиля (sslConfigName) не указано, то используются значения паролей из секции sslConfigData.default.

Эта логика реализуется в коде Шлюза и не меняется.

Если значение настроек sslConfigs[].sslSocketFactory.keyStorePassword и sslConfigs[].sslSocketFactory.trustStorePassword задано, а настройки в блоке sslConfigData не заданы, то будут использованы настройки из sslConfigs[].

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

Шлюз обеспечивает приоритет загрузки конфигурации (в частности секции sslConfigs) из файлов environment.yml (используется в некоторых проектах), application.yml, общий механизм загрузки при этом остается без изменений.

Это значит, что если одноименные параметры настройки заданы в нескольких файлах конфигурации, то используются значения заданные в application.yml, если там нет, то в environment.yml, если нет и там, то в ssl-secret.yml.

Маршрутизация gRPC-вызовов в Шлюзе MQ#

Маршрутизация на Шлюзе MQ - это определение имени сервиса-получателя (точки назначения), которому необходимо направить gRPC-вызов с преобразованным в формат ProtoMessage входящим сообщением.

В Шлюзе MQ реализована динамическая маршрутизация по гибким правилам, на основании параметров входящего MQ-сообщения.

Механизм маршрутизации работает следующим образом:

В настройках Шлюза MQ в блоке gRPC прописывается наименование сервиса-заглушки empty-service:

grpc:
  client:
    settings:
      default:
        hostname: empty-service
        port: 5454

При выполнении gRPC-вызова он перенаправляется механизмами OpenShift в сервис, имя которого задано в параметре вызова Authority.

Шлюз MQ позволяет динамически задать значение этого параметра при вызове, применяя правила, заданные в конфигурации Шлюза MQ к параметрам (элементам тела и транспортным заголовкам) входящего сообщения.

Правила определения Authority задаются в конфигурации шлюза в блоке routing.

Процесс маршрутизации сообщения состоит из нескольких шагов:

Шаг 1. Определение имени маршрута#

Правила определения имени маршрута задаются в секции routeName блока routing.

Секция определения имени маршрута (routeName):

routing:
  routeName:
    valueFrom:
      - type: fromRfh2Header
        value: OperationName
      - type: fromBody
        value: local-name(/*)
        valueJson: $.operation
      - type: fromConst
        value: SomeServiceRq
    expr: value.substring(0,value.length()-2)
  routeList:
...

В этой секции задается список правил, каждое правило состоит из пары type и value.

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

Для type= fromBody есть дополнительный тег valueJson, в котором можно задать выражение JsonPath для работы с сообщениями в формате JSON.

Возможные варианты:

type

описание

Значения value

fromHeader

Значение извлекается из блока системных заголовков. При работе с IBM MQ это соответствует блоку заголовков MQMD.

value должно содержать название системного (MQMD) заголовка, из которого будет взято значение

fromRfh2Header

Значение извлекается из блока пользовательских заголовков. При работе с IBM MQ это соответствует блоку заголовков MQRFH2

value должно содержать название пользовательского (MQRFH2) заголовка, из которого будет взято значение

fromBody

Значение будет извлечено из тела сообщения.

value должно содержать XPath запрос, которым из тела сообщения будет получено значение. Работает только с сообщениями в формате XML
valueJson должно содержать JsonPath запрос, которым из тела сообщения будет получено значение. Работает только с сообщениями в формате JSON

fromConst

Значение задается константой непосредственно в конфигурации.

value должно содержать непосредственно нужное значение

Для повышения гибкости определения маршрута в секции routeName есть параметр expr, в котором можно задать выражение для дополнительной коррекции полученного значения. Обращение к найденному значению выполняется через предопределенную переменную value. Например, выражение value.substring(0,value.length()-2) позволяет обрезать два последних символа в полученном значении.

Шлюз MQ применяет правила к входящему сообщению по порядку их следования в конфигурации до тех пор, пока не будет получено ненулевое значение. Поэтому правило c типом fromConst следует ставить в конце списка.

К найденному значению Шлюз MQ применяет выражение, заданное в expr. Полученное в результате этих действий значение и будет являться именем маршрута routeName.

Пример:

Настройки шлюза заданы, как указано выше.

Для сообщения с параметрами:

  <MQRFH2>
    <usr>
      <OperationName>OneServiceRq</OperationName>
    </usr>
  </MQRFH2>
  <MSG_BODY>
    <Message>
      <RqUID>4382730B7D4111ebB66EFA163E29F519</RqUID>
      <RqTm>2021-03-05T08:44:09.000+00:00</RqTm>
      <Mode>Async</Mode>
...

в routeName будет получено значение: OneService.

Шаг 2. Поиск блока настроек в списке маршрутов routeList#

Полученное на предыдущем шаге имя маршрута сопоставляется со списком элементов следующей секции блока routing - routeList.

Секция правил определения Authority (routeList):

routeList:
  - routeName: AnotherService
    destinationExpression: 'system1-anotherservice-system2-rs'
  - routeName: default
    destinationExpression:  variables['SCName'] + '-' + variables['ServiceName'] + '-' + variables['SPName']
    variables:
      - name: ServiceName
        valueFrom:
          - type: fromBody
            value: local-name(/*)
        expr: value.substring(0,value.length()-2)
      - name: SCName
        valueFrom:
          - type: fromRfh2Header
            value: SCName
          - type: fromConst
            value: 'System1'
        expr: value.toLowerCase()
      - name: SPName
        valueFrom:
          - type: fromRfh2Header
            value: SPName
          - type: fromBody
            value: //SystemID
        expr: value+'-rs'
    destinations:
      'system1-someservice-system2-rs': 'system1-someservice-system2-async-rs'

Каждый элемент этой секции содержит именованный набор правил вычисления Authority сервиса назначения и состоит из параметров:

Параметр

Описание

routeName

Имя маршрута, сопоставляется с полученным значением routeName

destinationExpression

Задает выражение, для непосредственного вычисления Authority из элементов variables

variables

Задает список именованных переменных, используемых в destinationExpression, и правил определения их значений.
Правила аналогичны используемым для определения routeName

destinations

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

Если в списке routeList найден блок с именем, соответствующим полученному routeName, то он используется для определения Authority.

Если нужный routeName не найден, но задан блок с routeName: default, то для определения Authority будет использован он. Если и блок default не задан, то будет сгенерировано исключение.

Секция правил определения Authority (routeList):

routeList:
  - routeName: AnotherSe
```yml
variables:
  - name: ServiceName
    valueFrom:
      - type: fromBody
        value: local-name(/*)
    expr: value.substring(0,value.length()-2)
  - name: SCName
    valueFrom:
      - type: fromRfh2Header
        value: SCName
      - type: fromConst
        value: 'System1'
    expr: value.toLowerCase()

Принцип применения type, value и expr был детально рассмотрен выше, при описании вычисления routeName. Например, для приведенного примера значение параметра variables['ServiceName'] будет взято из корневого тега сообщения с обрезанием двух последних символов.

Шаг 4. Определение destinationExpression#

Полученные на шаге 3 значения параметров Шлюз MQ подставляет в выражение, заданное в destinationExpression и вычислив его, получает Authority вызываемого сервиса.

В частном случае destinationExpression может быть задано константой.

Шаг 5. Подстановка#

В блоке правил может быть задан список destinations.

destinations:
  'system1-someservice-system2-rs': 'system1-someservice-system2-async-rs'

Если он задан, то Шлюз MQ производит в нем поиск по наименованию значения, полученного на шаге 4. Если поиск успешен, то Шлюз MQ производит замену Authority на значение из найденного элемента. Если список destinations не задан или поиск в нем неудачен, то используется значение, полученное на шаге 4.

Шаг 6. Выполнение вызова#

Шлюз MQ выполняет вызов сервиса по найденному Authority.

Примечание

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

Правила определения очереди и менеджера сообщения при отправке сообщения в MQ#

Возможность изменения назначения по умолчанию (задаваемого через настройки шлюза mq.connection.sendQueue, mq.connection.connections.queueManager) активируется настройкой mq.connection.sendToCustomDestination = true.

На определение конкретной точки назначения при отправке сообщения в MQ влияет наличие во входящем gRPC-вызове и Proto-сообщении следующих заголовков:

Заголовок

Положение

Описание

x-synapse-override-destination-queue

gRPC-вызов

Заголовок в контексте gRPC-вызова.
Если заполнен, сообщение будет отправлено в очередь, указанную в этом заголовке.

x-synapse-override-destination-manager

gRPC-вызов

Заголовок в контексте gRPC-вызова.
Если заполнен, и заполнен x-synapse-override-destination-queue, сообщение будет отправлено в очередь и менеджер, указанные в этой паре заголовков.

destinationQueue

ProtoMessage.extension

Поле в структуре типа MqMessageInformation, расширяющей ProtoMessage.
Если заполнен, и не заполнен x-synapse-override-destination-queue, сообщение будет отправлено в очередь, указанную в этом заголовке.

destinationManager

ProtoMessage.extension

Поле в структуре типа MqMessageInformation, расширяющей ProtoMessage.
Если заполнен, заполнен destinationQueue и не заполнен x-synapse-override-destination-queue, сообщение будет отправлено в очередь и менеджер, указанные в destinationQueue и destinationManager.

ReplyToQ

ProtoMessage.systemHeaders

Заголовок в списке ProtoMessage.systemHeaders.
Если заполнен, и не заполнены x-synapse-override-destination-queue и destinationQueue, сообщение будет отправлено в очередь, указанную в этом заголовке.

ReplyToQMgr

ProtoMessage.systemHeaders

Заголовок в списке ProtoMessage.systemHeaders.
Если заполнен, заполнен ReplyToQ и не заполнены x-synapse-override-destination-queue и destinationQueue, сообщение будет отправлено в отправлено в очередь и менеджер, указанные в ReplyToQ и ReplyToQMgr.

sendQueue

application.yml (Блок mq)

Параметр в настройках Шлюза MQ.
Если заполнен и не заполнены x-synapse-override-destination-queue, destinationQueue и ReplyToQ, сообщение будет отправлено в очередь, указанную в этом параметре. Если кроме этого заполнен ReplyToQMgr, то сообщение будет отправлено в отправлено в очередь и менеджер, указанные в sendQueue и ReplyToQMgr.

Важно! Настройка mq.connection.connections.queueManager, заголовки x-synapse-override-destination-manager,destinationManager, ReplyToQMgr используются только при работе с провайдером IBM MQ.

При работе с провайдером IBM MQ, во всех случаях, когда определена очередь, но не определен менеджер, сообщение отправляется без указания менеджера. В этих случаях менеджер назначения определяется кластером MQ.

Блок-схема алгоритма маршрутизации в MQ:

Блок-схема определения очереди и менеджера назначения Схема. Определение очереди и менеджер назначения

Спецификация интерфейсов#

См. документ «Спецификация интерфейсов».

Преобразование интерфейсов#

См. документ «Преобразование интерфейсов».

Порядок настройки Шлюза MQ#

См. документ «Порядок настройки Шлюза MQ».

Полное описание настроек#

См. документ Полное описание настроек.

Часто встречающиеся проблемы и пути их устранения#

Проблемы, возникающие у прикладного разработчика в процессе разработки конечного продукта, аналогичны проблемам при эксплуатации компонента, описанным в разделе "Часто встречающиеся проблемы и пути их устранения" в Руководстве по системному администрированию.