Hyperledger Fabric外部链码构建与运行
外部链码构建与运行
官方文档
在Hyperledger Fabric 2.0版本之前,链码的构建和运行是节点实现的一部分,并且定制化是困难的。所有链码在节点上实例化是通过”构建“即根据语言指定的逻辑在节点上硬编码。构建过程将生成Docker
容器镜像作为客户端连接节点用来运行可执行的链码。
这种方法将链代码实现限制为只能使用几种语言实现,要求Docker
成为部署环境的一部分,并阻止将链代码作为长时间运行的服务器进程运行。
外部构建模式
Hyperledger Fabric外部构建器和启动器大致基于Heroku Buildpacks。buildpack
实现只是将程序归档转换为可以运行的程序或脚本的集合。buildpack
模型已针对链码包进行了调整,并扩展为支持链码执行和发现。
外部构建和运行API
外部构建和运行由4个脚本程序组成:
bin/detect
:确定是否应使用此buildpack
来构建chaincode
程序包并启动它bin/build
:转换链码包为可执行的链码bin/release(可选)
:为peer
节点提供关于链码的元数据bin/run(可选)
:运行链码
bin/detect
bin/detect
脚本决定是否应使用此buildpack
来构建chaincode
程序包并启动它,peer
节点通过两个参数调用detect
:
bin/detect CHAINCOD_SOURCE_DIR CHAINCODE_METADATA_DIR
当detect
被调用,CHAINCOD_SOURCE_DIR
包含的链码资源以及CHAINCODE_METADATA_DIR
包含的metadata.json
文件将从链码包中安装到节点。CHAINCOD_SOURCE_DIR
和CHAINCODE_METADATA_DIR
应该被作为只读输入。如果将buildpack
应用于chaincode
源程序包,detect
必须返回退出码0;否则,其他任何退出代码都将指示buildpack
不应用内于chaincode
源程序包。
下面是一个简单的用于go
语言链码的detect
脚本例子:
#!/bin/bash
CHAINCODE_METADATA_DIR="$2"
# 使用jq工具从metadata.json中获取链码类型,如果链码类型为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
被调用,CHAINCOD_SOURCE_DIR
包含的链码资源以及CHAINCODE_METADATA_DIR
包含的metadata.json
文件将从链码包中安装到节点。BUILD_OUTPUT_DIR
是一个文件夹用于存放release
和run
需要的文件。build
脚本应该将CHAINCOD_SOURCE_DIR
和CHAINCODE_METADATA_DIR
作为只读输入,BUILD_OUTPUT_DIR
作为可写输出。
下面是一个简单的用于go
语言链码的build
脚本例子:
#!/bin/bash
CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"
# 从 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
# 存储状态数据库索引元数据提供给release
if [ -d "$CHAINCODE_SOURCE_DIR/META-INF" ]; then
cp -a "$CHAINCODE_SOURCE_DIR/META-INF" "$BUILD_OUTPUT_DIR/"
fi
bin/release
bin/release
脚本为节点提供链码元数据。该脚本是可选的。如果没有提供,这一步将会跳过。节点通过两个参数调用release
:
bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR
调用release
时,BUILD_OUTPUT_DIR
包含构建程序填充的归档,应将其视为只读输入。RELEASE_OUTPUT_DIR
是release
必须放置归档以供节点使用的目录。
当release
执行完成,节点将会从RELEASE_OUTPUT_DIR
消费两种类型的元数据:
- CouchDB定义的状态数据库索引。
- 外部链码服务连接信息(
chaincode/server/connection.json
)
如果链码要求CouchDB索引定义,release
需要将索引放置在RELEASE_OUTPUT_DIR
下的state/couchdb/indexes
文件夹内。索引必须含有.json
扩展名。
在使用链码服务器实现的情况下,release
负责使用链码服务器的地址以及与链码通信所需的任何TLS资产来填充chaincode/server/connection.json
。将服务器连接信息提供给节点时,将不会调用run
。
下面是一个简单的用于go
语言链码的release
脚本例子:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RELEASE_OUTPUT_DIR="$2"
# 从 META-INF/* 拷贝索引文件到输出文件夹
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
当BUILD_OUTPUT_DIR
包含build
程序填充的归档,而RUN_METADATA_DIR
包含有一个名为chaincode.json
的文件,该文件包含链码连接和注册到节点所需的信息,run
将被调用。bin/run
脚本对于BUILD_OUTPUT_DIR
以及RUN_METADATA_DIR
文件夹应为只读输入。chaincode.json
文件包含的关键信息有:
chaincode_id:
连接到链码包的唯一IDpeer_address:``peer
节点的ChaincodeSupport
中的gRPC服务端点主机地址,格式为host:port
.client_cert:
由peer
生成的PEM
编码的TLS客户端证书。当链码与节点建立连接时将会被使用。client_key:
由peer
生成的PEM
编码的客户端秘钥。当链码与节点建立连接时将会被使用。root_cert:
由peer
节点的ChaincodeSupport
中的gRPC服务端点主机使用的PEM
编码的TLS
根证书。
当run
停止时,与peer
连接的链码也会终止。如果另一个请求访问链码,节点将会尝试通过调用run
启动链码的另一个实例。在调用链码时,chaincode.json
文件内容不能够被缓存。
下面是一个简单的用于go
语言链码的run
脚本例子:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RUN_METADATA_DIR="$2"
# 为go语言链码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"
# 为go语言链码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 "$BUILD_OUTPUT_DIR/chaincode" -peer.address="$(jq -r .peer_address "$ARTIFACTS/chaincode.json")"
外部构建和运行的配置
在core.yaml
的chaincode
配置区域下添加一个externalBuilder
元素配置节点以使用外部构建器.每一个外部构建器的定义必须包含名字(用于日志)和包含构建器脚本的bin
文件夹的上一级路径。
调用外部构建器脚本时还可以从节点获取环境变量名称的可选列表。
下面的示例定义了两个外部构建器:
chaincode:
externalBuilders:
- name: my-golang-builder
path: /builders/golang
environmentWhitelist:
- GOPROXY
- GONOPROXY
- GOSUMDB
- GONOSUMDB
- name: noop-builder
path: /builders/binary
在这个示例中,实现的构建器my-golang-builder
被包含在/builders/golang
文件夹内,它的脚本文件位于/builders/golang/bin
.当节点调用任何与my-golang-builder
相关的构建脚本时,将只会传播白名单内的环境变量的值。
这些环境变量总是传播到外部构建器:
- LD_LIBRARY_PATH
- LIBPATH
- PATH
- TMPDIR
当externalBuilder
配置存在时,节点将会迭代按顺序排列的构建器的列表。调用/bin/detect
直到其中的一个成功执行。
如果没有构建器成功执行detect
脚本,节点将会回滚使用初始的Docker
通过节点实现构建进程。这说明外部的构建器是完全可选的。
在上面的示例中,节点将试图使用my-golang-builder
,如果无效的话则使用noop-builder
,还是无效的话最后使用节点内部构建进程。
链码包
Fabric 2.0引入了新的生命周期链码。链码包的格式从序列号协议缓冲消息变为了由gzip
压缩的POSIX tape
归档。链码包通过使用peer lifecycle chaincode package
创建新的格式。
Lifecycle
链码包内容
lifecycle
链码包包含两个文件,第一个文件code.tar.gz
是一个使用gzip
压缩的POSIX tape
归档。这个文件包括链码的源归档。由节点CLI
创建并将链码的实现源码放置在src
文件夹下,链码的元数据(如CouchDB索引文件)放置在META-INF
文件夹。
第二个文件metadata.json
是一个JSON
格式的文档包含三个键:
type
:链码的类型(例如GOLANG
,JAVA
,NODE
)path
:对于go语言链码,则是GOPATH
或者GOMOD
到主链码包的相对路径,其他类型的链码未定义。label
:用于生成包ID的链码标签,在新的链码lifecycle
过程中用于标识包的身份。
type
和path
字段仅由Docker
平台构建使用。
链码包以及外部构建器
当链码包安装在节点上后,code.tar.gz
和metadata.json
的内容将不能调用外部构建器处理。除了label
字段用于新的lifecycle
对包ID进行计算。为用户提供了很大的灵活性,使他们可以打包将由外部构建者和启动者处理的源和元数据。
例如,可以构造一个自定义的链码包,该代码包在code.tar.gz
中包含一个预编译的链码实现,并带有一个metadata.json
文件,允许二进制构建包检测该自定义包,验证哈希值并作为链码运行。
另一个示例是chaincode程序包,其中仅包含状态数据库索引定义以及外部启动程序连接到正在运行的chaincode
服务器所需的数据。在这种情况下,build
过程将仅从过程中提取元数据,然后将其release
给节点。
唯一的要求是code.tar.gz
只能包含常规文件和目录条目,并且这些条目不能包含会导致文件写入链码包根路径逻辑外。