# Внешние модули сборки и запуска До выхода версии Hyperledger Fabric 2.0 процесс сборки и запуска чейнкодов был частью реализации однорангового узла и не мог быть просто изменен. Все чейнкоды, установленные на одноранговый узел, "собирались" по специфичной для языка программирования логике, жестко определенной в самом узле. В результате такого процесса сборки создается образ контейнера Docker, который запускается для выполнения чейнкода, подключенного к одноранговому узлу в качестве клиента. Такой подход ограничивал реализацию чейнкодов только несколькими языками программирования, требовал, чтобы Docker был частью среды, в которой происходит развертывание, и не позволял запускать чейнкоды как долго работающие серверные процессы. Начиная с версии Fabric 2.0 внешние модули сборки и запуска устраняют эти ограничения, позволяя операторам расширять одноранговые узлы программами, которые могут собирать, запускать и обнаруживать чейнкоды. Для использования этой возможности, необходимо создать свой собственный модуль сборки (buildpack), а затем включить его в раздел `externalBuilders` конфигурации однорангового узла в файле core.yaml, чтобы узел знал о наличии этого модуля. Далее описаны детали этого процесса. Обратите внимание, что если ни один из включенных в конфигурацию внешних модулей не может осуществить сборку пакета чейнкода, одноранговый узел попытается обработать этот пакет так, как будто он был создан стандартными инструментами упаковки Fabric, такими как команды CLI или функции SDK. **Примечание:** Это расширенная функция, которая скорее всего потребует пользовательской сборки образа однорангового узла. Например, далее в статье используются `go` и `bash`, которые не включены в текущий официальный образ `fabric-peer`. ## Модель внешнего модуля сборки Внешние модули сборки и запуска чейнкодов Hyperledger Fabric в значительной степени основаны на модулях сборки Heroku ([Buildpacks](https://devcenter.heroku.com/articles/buildpack-api)). Реализация этих модулей сборки - это просто набор программ или скриптов, которые превращают артефакты приложения в нечто, что можно запустить. Модель модуля сборки была адаптирована для пакетов чейнкода и расширена для поддержки выполнения и обнаружения чейнкодов. ### API внешних модулей сборки и запуска Внешний модуль сборки и запуска состоит из четырех программ или скриптов: - `bin/detect` определяет, следует ли использовать данный модуль для сборки пакета чейнкода и его запуска. - `bin/build` преобразует пакет чейнкода в исполняемый чейнкод. - `bin/release` (опционально) предоставляет метаданные о чейнкоде одноранговому узлу. - `bin/run` (опционально) запускает чейнкод. #### `bin/detect` Скрипт `bin/detect` отвечает за определение того, стоит ли использовать данный модуль для сборки пакета чейнкода и его запуска. Одноранговый узел вызывает `detect` с двуми аргументами: ```sh 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: ```sh #!/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` с тремя аргументами: ```sh 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: ```sh #!/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` с двумя аргументами: ```sh 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 в качестве базы данных состояния](couchdb_as_state_database.html#couchdb-indexes). В случаях использования чейнкода как сервера `release` отвечает за заполнение файла `chaincode/server/connection.json` адресом сервера чейнкода и данными TLS, необходимыми для связи с ним. При предоставлении этой информации скрипт `run` не будет вызываться. Подробнее о сервере чейнкода вы можете прочитать на [этой странице](https://jira.hyperledger.org/browse/FAB-14086) Ниже приведен пример простого скрипта `release` для чейнкода, написанного на языке go: ```sh #!/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` с двумя аргументами: ```sh 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: ```sh #!/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` со скриптами модуля сборки. Также при необходимости может быть указан список имен переменных окружения, которые будут передаваться из однорангового узла при вызове скриптов модуля сборки. В следующем примере определены два внешних модуля сборки: ```yaml 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` может содержать только обычные файлы и каталоги, и что они не могут содержать пути, которые приведут к записи файлов вне логического корня пакета чейнкода.