Внешние модули сборки и запуска

До выхода версии Hyperledger Fabric 2.0 процесс сборки и запуска чейнкодов был частью реализации однорангового узла и не мог быть просто изменен. Все чейнкоды, установленные на одноранговый узел, «собирались» по специфичной для языка программирования логике, жестко определенной в самом узле. В результате такого процесса сборки создается образ контейнера Docker, который запускается для выполнения чейнкода, подключенного к одноранговому узлу в качестве клиента.

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

Начиная с версии Fabric 2.0 внешние модули сборки и запуска устраняют эти ограничения, позволяя операторам расширять одноранговые узлы программами, которые могут собирать, запускать и обнаруживать чейнкоды. Для использования этой возможности, необходимо создать свой собственный модуль сборки (buildpack), а затем включить его в раздел externalBuilders конфигурации однорангового узла в файле core.yaml, чтобы узел знал о наличии этого модуля. Далее описаны детали этого процесса.

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

Примечание: Это расширенная функция, которая скорее всего потребует пользовательской сборки образа однорангового узла. Например, далее в статье используются go и bash, которые не включены в текущий официальный образ fabric-peer.

Модель внешнего модуля сборки

Внешние модули сборки и запуска чейнкодов Hyperledger Fabric в значительной степени основаны на модулях сборки Heroku (Buildpacks). Реализация этих модулей сборки - это просто набор программ или скриптов, которые превращают артефакты приложения в нечто, что можно запустить. Модель модуля сборки была адаптирована для пакетов чейнкода и расширена для поддержки выполнения и обнаружения чейнкодов.

API внешних модулей сборки и запуска

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

  • bin/detect определяет, следует ли использовать данный модуль для сборки пакета чейнкода и его запуска.
  • bin/build преобразует пакет чейнкода в исполняемый чейнкод.
  • bin/release (опционально) предоставляет метаданные о чейнкоде одноранговому узлу.
  • bin/run (опционально) запускает чейнкод.

bin/detect

Скрипт bin/detect отвечает за определение того, стоит ли использовать данный модуль для сборки пакета чейнкода и его запуска. Одноранговый узел вызывает detect с двуми аргументами:

bin/detect CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR

При вызове detect директория CHAINCODE_SOURCE_DIR содержит исходный код чейнкода, а CHAINCODE_METADATA_DIR - файл metadata.json из установленного на одноранговый узел пакета чейнкода. Директории CHAINCODE_SOURCE_DIR и CHAINCODE_METADATA_DIR следует рассматривать как входные данные, доступные только для чтения. Если данный модуль сборки должен быть применен к исходному пакету чейнкода, скрипт detect должен вернуть код 0; любой другой код завершения будет означать, что модуль сборки не должен быть применен.

Ниже приведен пример простого скрипта detect для чейнкода, написанного на языке go:

#!/bin/bash

CHAINCODE_METADATA_DIR="$2"

# use jq to extract the chaincode type from metadata.json and exit with
# success if the chaincode type is golang
if [ "$(jq -r .type "$CHAINCODE_METADATA_DIR/metadata.json" | tr '[:upper:]' '[:lower:]')" = "golang" ]; then
    exit 0
fi

exit 1

bin/build

Скрипт bin/build отвечает за сборку, компиляцию или преобразование содержимого пакета чейнкода в артефакты, которые могут быть использованы скриптами release и run. Одноранговый узел вызывает build с тремя аргументами:

bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR

При вызове build директория CHAINCODE_SOURCE_DIR содержит исходный код чейнкода, а CHAINCODE_METADATA_DIR - файл metadata.json из установленного на одноранговый узел пакета чейнкода. BUILD_OUTPUT_DIR - это директория, в которую скрипт build должен поместить артефакты, необходимые для скриптов release и run. Директории CHAINCODE_SOURCE_DIR и CHAINCODE_METADATA_DIR должны рассматриваться как доступные только для чтения, но директория BUILD_OUTPUT_DIR доступна для записи.

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

Ниже приведен пример простого скрипта build для чейнкода, написанного на языке go:

#!/bin/bash

CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"

# extract package path from metadata.json
GO_PACKAGE_PATH="$(jq -r .path "$CHAINCODE_METADATA_DIR/metadata.json")"
if [ -f "$CHAINCODE_SOURCE_DIR/src/go.mod" ]; then
    cd "$CHAINCODE_SOURCE_DIR/src"
    go build -v -mod=readonly -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
else
    GO111MODULE=off go build -v  -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
fi

# save statedb index metadata to provide at release
if [ -d "$CHAINCODE_SOURCE_DIR/META-INF" ]; then
    cp -a "$CHAINCODE_SOURCE_DIR/META-INF" "$BUILD_OUTPUT_DIR/"
fi

bin/release

Скрипт bin/release отвечает за предоставление метаданных чейнкода одноранговому узлу. bin/release является необязательным. Если он отсутствует, этот шаг пропускается. Одноранговый узел вызывает release с двумя аргументами:

bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR

При вызове release директория BUILD_OUTPUT_DIR содержит артефакты, созданные программой build и рассматривается как входные данные только для чтения. RELEASE_OUTPUT_DIR - директория, в которую скрипт release должен поместить артефакты для использования одноранговым узлом.

После завершения работы release одноранговому узлу доступно два типа метаданных из директории RELEASE_OUTPUT_DIR:

  • определение индексов CouchDB для базы данных состояния;
  • информация о подключении к внешнему серверу чейнкода (chaincode/server/connection.json).

Если определение индексов CouchDB требуется чейнкодом, скрипт release отвечает за их размещение в директории statedb/couchdb/indexes внутри RELEASE_OUTPUT_DIR. Файлы с индексами должны иметь расширение .json. Подробнее об этом читайте в статье об использовании CouchDB в качестве базы данных состояния.

В случаях использования чейнкода как сервера release отвечает за заполнение файла chaincode/server/connection.json адресом сервера чейнкода и данными TLS, необходимыми для связи с ним. При предоставлении этой информации скрипт run не будет вызываться. Подробнее о сервере чейнкода вы можете прочитать на этой странице

Ниже приведен пример простого скрипта release для чейнкода, написанного на языке go:

#!/bin/bash

BUILD_OUTPUT_DIR="$1"
RELEASE_OUTPUT_DIR="$2"

# copy indexes from META-INF/* to the output directory
if [ -d "$BUILD_OUTPUT_DIR/META-INF" ] ; then
   cp -a "$BUILD_OUTPUT_DIR/META-INF/"* "$RELEASE_OUTPUT_DIR/"
fi

bin/run

Скрипт bin/run отвечает за запуск чейнкода. Одноранговый узел вызывает run с двумя аргументами:

bin/run BUILD_OUTPUT_DIR RUN_METADATA_DIR

При вызове run директория BUILD_OUTPUT_DIR содержит артефакты, созданные программой build, а в директории RUN_METADATA_DIR содержится файл с именем chaincode.json, содержащий информацию, необходимую чейнкоду для соединения с одноранговым узлом и регистрации на нем. Обратите внимание, что скрипт bin/run должен рассматривать содержимое директорий BUILD_OUTPUT_DIR и RUN_METADATA_DIR как входные данные только для чтения. В файле chaincode.json содержатся следующие ключи:

  • chaincode_id: уникальный идентификатор, связанный с пакетом чейнкода.
  • peer_address: адрес в формате host:port конечной точки сервера gRPC ChaincodeSupport, расположенного на одноранговом узле.
  • client_cert: сертификат клиента в кодировке PEM, созданный одноранговым узлом, который должен использоваться при установке соединения TLS между чейнкодом и одноранговым узлом.
  • client_key: ключ в кодировке PEM, созданный одноранговым узлом, который должен использоваться при уставновке соединения TLS между чейнкодом и одноранговым узлом.
  • root_cert: корневой сертификат в кодировке PEM для конечной точки сервера gRPC ChaincodeSupport, расположенного на одноранговом узле.
  • mspid: локальный mspid однорангового узла.

Когда run завершает свою работу, одноранговый узел считает работу чейнкода завершенной. Если для чейнкода поступает другой запрос, одноранговый узел пытается запустить еще один экземпляр чейнкода, снова вызвав run. Содержимое файла chaincode.json не должно кэшироваться при каждом вызове.

Ниже приведен пример простого скрипта run для чейнкода, написанного на языке go:

#!/bin/bash

BUILD_OUTPUT_DIR="$1"
RUN_METADATA_DIR="$2"

# setup the environment expected by the go chaincode shim
export CORE_CHAINCODE_ID_NAME="$(jq -r .chaincode_id "$RUN_METADATA_DIR/chaincode.json")"
export CORE_PEER_TLS_ENABLED="true"
export CORE_TLS_CLIENT_CERT_FILE="$RUN_METADATA_DIR/client.crt"
export CORE_TLS_CLIENT_KEY_FILE="$RUN_METADATA_DIR/client.key"
export CORE_PEER_TLS_ROOTCERT_FILE="$RUN_METADATA_DIR/root.crt"
export CORE_PEER_LOCALMSPID="$(jq -r .mspid "$RUN_METADATA_DIR/chaincode.json")"

# populate the key and certificate material used by the go chaincode shim
jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_CERT_FILE"
jq -r .client_key  "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_KEY_FILE"
jq -r .root_cert   "$RUN_METADATA_DIR/chaincode.json" > "$CORE_PEER_TLS_ROOTCERT_FILE"
if [ -z "$(jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json")" ]; then
    export CORE_PEER_TLS_ENABLED="false"
fi

# exec the chaincode to replace the script with the chaincode process
exec "$BUILD_OUTPUT_DIR/chaincode" -peer.address="$(jq -r .peer_address "$ARTIFACTS/chaincode.json")"

Настройка внешний модулей сборки и запуска

Настройка одногрангового узла на использование внешних модулей сборки заключается в добавлении нового элемента в блок конфигурации чейнкода externalBuilders в файле core.yaml. Каждое определение внешнего модуля сборки должно включать имя (используемое для логирования) и путь к директории, содержащей директорию bin со скриптами модуля сборки.

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

В следующем примере определены два внешних модуля сборки:

chaincode:
  externalBuilders:
  - name: my-golang-builder
    path: /builders/golang
    propagateEnvironment:
    - GOPROXY
    - GONOPROXY
    - GOSUMDB
    - GONOSUMDB
  - name: noop-builder
    path: /builders/binary

В этом примере реализация модуля сборки «my-golang-builder» расположена в директории /builders/golang, а его скрипты содержатся в /builders/golang/bin. Когда одноранговый узел будет вызывать один и скриптов сборки, связанных с «my-golang-builder», он будет передавать только значения переменных из списка propagateEnvironment.

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

  • LD_LIBRARY_PATH
  • LIBPATH
  • PATH
  • TMPDIR

Если в блоке externalBuilders конфигурации однорангового узла определены внешние модули сборки, узел будет в указанном порядке поочередно вызывать скрипт bin/detect каждого модуля, пока один из них не завершится успешно. Если ни один из них не завершится успешно, одноранговый узел вернется к обычному процессу сборки образов Docker, реализованному внутри узла. Это означает, что внешние модули сборки совершенно не обязательны.

В приведенном выше примере одноранговый узел сначала попытается использовать модуль сборки «my-golang-builder», затем модуль «noop-builder» и, наконец, свой внутренний процесс сборки.

Пакеты чейнкодов

В рамках нового жизненного цикла, представленного в Fabric 2.0, формат пакета чейнкода изменился с сериализованных данных (protocol buffers) на архив tar, сжатый gzip. Пакеты чейнкода, созданные с помощью команды peer lifecycle chaincode package, используют этот новый формат.

Содержание пакета жизненного цикла чейнкода

Пакет жизненного цикла чейнкода содержит два файла. Первый файл, code.tar.gz, представляет собой сжатый gzip архив tar. В нем содержатся исходные артефакты чейнкода. В пакетах, созданных с помощью команд CLI однорангового узла, исходный код чейнкода помещен в директорию src, а метаданные чейнкода (например, индексы CouchDB) - в каталог META-INF.

Второй файл, metadata.json, представляет собой документ JSON с тремя ключами:

  • type: тип чейкода (например, GOLANG, JAVA, NODE).
  • path: для чейнкода на go, путь к основному пакету чейнкода относительно GOPATH или GOMOD; не определен для других типов.
  • label: метка чейнкода, используемая при создании идентификатора пакета, по которому пакет чейнкода идентифицируется в новом процессе жизненного цикла.

Обратите внимание, что поля type и path используются только при сборках платформой docker.

Пакеты чейнкода и внешние модули сборки

Когда пакет чейнкода устанавливается на одноранговый узел, содержимое файлов code.tar.gz и metadata.json не обрабатывается до вызова внешних модулей сборки, за исключением поля label, которое используется новым процессом жизненного цикла для вычисления идентификатора пакета. Это дает пользователям большую гибкость в том, как они упаковывают исходный код и метаданные, которые будут обрабатываться внешними модулями сборки и запуска.

Например, можно создать пакет чейнкода, который содержит предварительно скомпилированную реализацию чейнкода в code.tar.gz и файл metadata.json, позволяющий бинарному модулю сборки обнаружить пользовательский пакет, проверить хеш-сумму бинарного файла и запустить программу как чейнкод.

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

Единственным требованием является то, что code.tar.gz может содержать только обычные файлы и каталоги, и что они не могут содержать пути, которые приведут к записи файлов вне логического корня пакета чейнкода.