fabric 版本:release-1.3
机器环境:
cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
#内核版本
uname -sr
Linux 3.10.0-514.26.2.el7.x86_64
#官方自带 Docker 版本
rpm -qa | grep docker
docker-common-1.13.1-84.git07f3374.el7.centos.x86_64
docker-client-1.13.1-84.git07f3374.el7.centos.x86_64
docker-1.13.1-84.git07f3374.el7.centos.x86_64
初步申请 5 台机器来搭建集群网络:
名称 | ip 地址 | 职责 |
---|---|---|
orderer.example.com | xx.xx.xx.x1 | 共识节点 |
peer0.org1.example.com | xx.xx.xx.x2 | 公司 1 peer0 |
peer1.org1.example.com | xx.xx.xx.x3 | 公司 1 peer1 |
peer0.org2.example.com | xx.xx.xx.x4 | 公司 2 peer0 |
peer1.org2.example.com | xx.xx.xx.x5 | 公司 2 peer1 |
1.初始化所有机器环境,安装依赖包,配置环境变量
yum install docker git -y
pip install docker-compose
mkdir -p /data/packages/
cd /data/packages
curl https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz -o go1.11.2.linux-amd64.tar.gz
tar -C /usr/local/ -xzf go1.11.2.linux-amd64.tar.gz
echo "PATH=\\$PATH:/usr/local/go/bin" >> /etc/bashrc
echo "export PATH" >> /etc/bashrc
echo "export GOPATH=/data/go" >> /etc/bashrc
source /etc/bashrc
# 在 peer 机器执行
mkdir -p $GOPATH/src/github.com/hyperledger/fabric/multipeer
2. 部署orderer
# 拉取 fabric 源码
mkdir -p $GOPATH/src/github.com/hyperledger/
git clone https://github.com/hyperledger/fabric.git && cd fabric
git checkout release-1.3
make release
mkdir -p multipeer/channel-artifacts && cd multipeer
cp ../examples/e2e_cli/crypto-config.yaml ../examples/e2e_cli/configtx.yaml ../examples/e2e_cli/generateArtifacts.sh .
# 调整 generateArtifacts.sh 脚本
# 调整路径 export FABRIC_ROOT=$PWD/..
# 注释掉该方法 #replacePrivateKey
# 生成证书相关的文件
./generateArtifacts.sh
cp ../examples/e2e_cli/docker-compose-cli.yaml docker-compose-order.yaml
cp -r ../examples/e2e_cli/base/ .
# 修改 docker-compose-cli.yaml 将 peer 和 cli 的配置删除
# 启动 orderer service
docker-compose -f docker-compose-orderer.yaml up -d 2>&1
# scp 文件到 peer 节点 前提是 peer 节点已创建好接收目录
cp -r ../examples/chaincode/ .
# 删除 chaincode/go/目录下除 examples02 目录外的其他目录,只保留 examples02
scp -r channel-artifacts/ crypto-config/ chaincode/ root@xx.xx.xx.x2:$GOPATH/src/github.com/hyperledger/fabric/multipeer
scp -r channel-artifacts/ crypto-config/ chaincode/ root@xx.xx.xx.x3:$GOPATH/src/github.com/hyperledger/fabric/multipeer
scp -r channel-artifacts/ crypto-config/ chaincode/ root@xx.xx.xx.x4:$GOPATH/src/github.com/hyperledger/fabric/multipeer
scp -r channel-artifacts/ crypto-config/ chaincode/ root@xx.xx.xx.x5:$GOPATH/src/github.com/hyperledger/fabric/multipeer
3. 部署peer xx.xx.xx.x1
touch docker-compose-peer.yaml
文件内容如下(特别注意黄色高亮的部分,不同 peer 可能会有不同):
# 启动 peer
docker-compose -f docker-compose-peer.yaml up -d
docker exec -it cli bash
# 设置变量
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
# 创建 channel
peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls --cafile $ORDERER_CA
# 加入 channel
peer channel join -b mychannel.block
# 安装 chaincode
peer chaincode install -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/example02/cmd/ -v 1.0
# 初始化 chaincode
peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.peer','Org2MSP.peer')"
# chaincode 查询 a
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
# chaincode a 向 b 转 10
peer chaincode invoke --tls --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["invoke","a","b","10"]}'
# chaincode 查询 b
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
# 将 docker 中生成的 mychannel.block 文件拷贝出来
docker cp [docker_container_id]:/opt/gopath/src/github.com/hyperledger/fabric/peer .
# 复制到其他 peer 节点
scp mychannel.block oot@xx.xx.xx.x3:$GOPATH/src/github.com/hyperledger/fabric/multipeer
scp mychannel.block oot@xx.xx.xx.x4:$GOPATH/src/github.com/hyperledger/fabric/multipeer
scp mychannel.block oot@xx.xx.xx.x5:$GOPATH/src/github.com/hyperledger/fabric/multipeer
4. peer xx.xx.xx.x3 4 5 节点
touch docker-compose-peer.yaml
内容和上个 peer 只有黄色高亮部分需调整,详情见附件
docker-compose -f docker-compose-peer.yaml up -d
# 查看 docker cli container id
docker ps
docker cp mychannel.block [docker_container_id]:/opt/gopath/src/github.com/hyperledger/fabric/peer
docker exec -it cli bash
peer channel join -b mychannel.block
peer chaincode install -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/example02/cmd/ -v 1.0
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
顺利的话,到这里就结束了,但实操过程中遇到了几个问题,列出如下:
a. 启动 peer 节点,执行 docker exec -it cli bash 时,抛出 runtime error
Cannot ssh into a running pod/container -- rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused "read parent: connection reset by peer"" command terminated with exit code 126
原因是 centOS7 官方 Docker 发行版 1.13.1-84 中有 bug 导致的,可以通过如下方式解决(bug 详情参见引用文章 1),降级到 1.13.1-75 版本即可,执行降级 docker 版本命令:
service docker stop
yum downgrade docker-1.13.1-75.git8633870.el7.centos.x86_64 docker-client-1.13.1-75.git8633870.el7.centos.x86_64 docker-common-1.13.1-75.git8633870.el7.centos.x86_64
service docker start
b. 在 peer 节点上执行 peer channel create …、peer channel list 均抛出类似下面的错误:
Error: failed to create deliver client: orderer client failed to connect to orderer.example.com:7050: failed to create new connection: context deadline exceeded
orderer service 服务日志没有显示有连接进来,研究半天无果,决定跑一边 fabric 自带的 e2e_cli 示例程序看看是否正常。
启动 e2e_cli 实例流程:
yum install docker git -y
pip install docker-compose
mkdir -p /data/packages/
cd /data/packages
curl https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz -o go1.11.2.linux-amd64.tar.gz
tar -C /usr/local/ -xzf go1.11.2.linux-amd64.tar.gz
echo "PATH=\\$PATH:/usr/local/go/bin" >> /etc/bashrc
echo "export PATH" >> /etc/bashrc
echo "export GOPATH=/data/go" >> /etc/bashrc
source /etc/bashrc
mkdir -p /data/go/src/github.com/hyperledger/
cd /data/go/src/github.com/hyperledger/
git clone https://github.com/hyperledger/fabric.git
cd fabric
git checkout release-1.3
cd examples/e2e_cli
service docker start
./network.sh up
结果同样没能正常启动,报如下异常:
____ _____ _ ____ _____ _____ ____ _____
/ ___| |_ _| / \ | _ \ |_ _| | ____| |___ \ | ____|
\___ \ | | / _ \ | |_) | | | _____ | _| __) | | _|
___) | | | / ___ \ | _ < | | |_____| | |___ / __/ | |___
|____/ |_| /_/ \_\ |_| \_\ |_| |_____| |_____| |_____|
Channel name : mychannel
Check orderering service availability...
Attempting to fetch system channel 'e2e-orderer-syschan' ...3 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...9 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...15 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...21 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...27 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...33 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...39 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...45 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...51 secs
Attempting to fetch system channel 'e2e-orderer-syschan' ...57 secs
2018-12-12 09:58:33.981 UTC [main] InitCmd -> WARN 001 CORE_LOGGING_LEVEL is no longer supported, please use the FABRIC_LOGGING_SPEC environment variable
2018-12-12 09:58:33.995 UTC [main] SetOrdererEnv -> WARN 002 CORE_LOGGING_LEVEL is no longer supported, please use the FABRIC_LOGGING_SPEC environment variable
Error: failed to create deliver client: orderer client failed to connect to orderer.example.com:7050: failed to create new connection: context deadline exceeded
!!!!!!!!!!!!!!! Ordering Service is not available, Please try again ... !!!!!!!!!!!!!!!!
================== ERROR !!! FAILED to execute End-2-End Scenario ==================
由于是单机不存在机器安全组问题,无法 connect 应该和 dns 有关,通过对比正常跑起来的机器/etc/resolv.conf 文件,发现如下差异:问题机器多了 options 这一行
options timeout:2 attempts:3 rotate single-request-reopen
在阿里云云栖平台搜索相关内容,果不其然,找到了余珊的这篇《阿里云环境部署 Hyperledger Fabric 之 SIGSEGV 问题分析和解决经验分享》 把问题的来龙去脉分析的很清楚了。
golang 支持两种类型的 dns 解析,即 pure go dns 和 cgo dns,其中 pure go 是使用纯 go 语言实现的 dns 解析,cgo 则是通过 c 语言实现,这两种实现的区别如下:
1)纯 GO 语言实现的域名解析,从/etc/resolv.conf 中取出本地 dns server 地址列表, 发送 DNS 请求(UDP 报文)并获得结果
2) 使用 cgo 方式, 最终会调用到 c 标准库的 getaddrinfo 或 getnameinfo 函数(不建议使用对 GO 协程不友好)
而 go 怎么选择使用哪种 DNS 来进行解析呢?
GO 语言默认使用纯 GO 的域名解析,因为这样一个阻塞的 DNS 请求只会消耗一个协程, 使 用 cgo 的方式则会阻塞一个系统线程, 只有某些特定条件下才会使用系统提供的 cgo 方式, 例如:
1) 在 OS X 系统中不允许程序直接发送 DNS 请求;
2) LOCALDOMAINH 环境变量存在,即使为空;
3) ES_OPTIONS 或 HOSTALIASES 或 ASR_CONFIG 环境变量非空;
4)/etc/resolv.conf 或/etc/nsswitch.conf 指定的使用方式 GO 解析器没有实现;
5)当要解析的域名以.local 结束, 或者是一个 mDNS 域名
我们的案例使用了 CGO,因为我们的案例符合第四种情况,见 golang 源码解析 options 的代码段:
case "options": // magic options
for _, s := range f[1:] {
switch {
case hasPrefix(s, "ndots:"):
n, _, _ := dtoi(s[6:])
if n < 0 {
n = 0
} else if n > 15 {
n = 15
}
conf.ndots = n
case hasPrefix(s, "timeout:"):
n, _, _ := dtoi(s[8:])
if n < 1 {
n = 1
}
conf.timeout = time.Duration(n) * time.Second
case hasPrefix(s, "attempts:"):
n, _, _ := dtoi(s[9:])
if n < 1 {
n = 1
}
conf.attempts = n
case s == "rotate":
conf.rotate = true
default:
conf.unknownOpt = true
}
}
问题机中包含了 Go 语言解析器未实现的 single-request-reopen(解决网页加载速度慢问题)。
ingle-request-reopen (since glibc 2.9)
Sets RES_SNGLKUPREOP in _res.options. The resolver
uses the same socket for the A and AAAA requests. Some
hardware mistakenly sends back only one reply. When
that happens the client system will sit and wait for
the second reply. Turning this option on changes this
behavior so that if two requests from the same port are
not handled correctly it will close the socket and open
a new one before sending the second request.
那为何使用了 CGO 的 DNS 解析策略会出问题呢?
这是因为 fabric 采用了静态编译,而 CGO 使用的 getaddrinfo 方法需要用到动态链接库,因此会在执行时出现问题。
make peer-docker orderer-docker
14:39:29 /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libltdl.a(dlopen.o): In function `vm_open':
14:39:29 (.text+0x5e): warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
14:39:29 /tmp/go-link-176411200/000000.o: In function `_cgo_b0c710f30cfd_C2func_getaddrinfo':
14:39:29 /tmp/go-build/net/_obj/cgo-gcc-prolog:46: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
fabric 官方推荐 set FABRIC_CA_DYNAMIC_LINK=true 来解决。当然我们也可以通过其他手段来使 Go 的 DNS 策略切换到默认的 Pure Go 的方式:
- 添加环境变量 GODEBUG=netdns=go
- 调整/etc/resolv.conf 里 options(谨慎操作)
参考文章:
- CentOS7 官方 Docker 发行版现重大 Bug 可致 Kubernetes 中的健康检测探针失败并影响容器交互 https://jimmysong.io/posts/docker-exec-bug-on-centos7/
- 阿里云环境部署Hyperledger Fabric 之 SIGSEGV 问题分析和解决经验分享 https://yq.aliyun.com/articles/238940
- resolv.conf 中的 options single-request-reopen 的详细解 http://www.man7.org/linux/man-pages/man5/resolver.5.html