在上一篇文章中,我们介绍了在 Kubernetes 上运行 Fabric 的机制和架构。 这篇文章会详细讲解安装的具体步骤。
准备环境 CMD 客户机
自己的 Mac(10.4.249.231)
可以通过 kubectl 操作远程的 Kubernetes 集群
安装 Python3,部署脚本是用 Python 写的
集群环境
CentOS7
Kubernetes v1.11.0
Docker 18.03.1-ce
Fabric 1.2.0
NFS
给集群做共享存储,挂载证书和一些 channel 文件。
代码 本文用到的代码在 https://github.com/batizhao/fabric-on-kubernetes
1 2 3 4 5 6 7 fabric-on-kubernetes |--README.md |--generateALL.sh // 生成 K8S yaml file |--transform // 使用 kubectl 部署或者卸载 Fabric |--templates // K8S yaml 模板 |--crypto-config.yaml // Fabric 集群配置文件 |--configtx.yaml // channel 和创世块配置
配置文件 A. crypto-config.yaml cryptogen 工具根据 crypto-config.yaml 来生成 Fabric 成员的证书,一个简单的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 OrdererOrgs: - Name: Orderer Domain: orgorderer1 Specs: - Hostname: orderer0 PeerOrgs: - Name: Org1 Domain: org1 EnableNodeOUs: true Template: Count: 2 Users: Count: 1 - Name: Org2 Domain: org2 EnableNodeOUs: true Template: Count: 2 Users: Count: 1
其中 OrdererOrgs 和 PeerOrgs 关键字区分 organization 的类型,两种组织的内部结构如下:
OrdererOrgs 中定义了一个名字为 Orderer ,域名为 orgorderer1 的 org 。
PeerOrgs 中定义了两个 org ,分别为 Org1 和 Org2 ,对应的域名为 org1、 org2 与 orderer 类似,每个 org 生成了两个 peers ,虽然 org1 中 peer0 和 org2 中 peer0 的 ID 重复,但是他不属于同一个 org ,通过域名很容易就能区分出它们。
需要注意的是,由于 K8S 中的 namespace 不支持 ‘.’ 和大写字母,因此各个组织的域名不能包含这些字符。
更多关于 crypto-config.yaml 的配置方式,请参考 Fabric 源码中的关于 cryptogen 的描述 ( fabric/common/tools/cryptogen/main.go)
cryptogen 工具会生成 crypto-config 目录,该目录的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 crypto-config |--- ordererOrganizations | |--- orgorderer1 | |--- msp | |--- ca | |--- tlsca | |--- users | |--- orderers | |--- orderer0.orgorderer1 | |--- msp | |--- tls | |--- peerOrganizations |--- org1 | |--- msp | |--- ca | |--- tlsca | |--- users | |--- peers | |--- peer0.org1 | | |--- msp | | |--- tls | |--- peer1.org1 | |--- msp | |--- tls |--- org2 |--- msp |--- ca |--- tlsca |--- users |--- peers |--- peer0.org2 | |--- msp | |--- tls |--- peer1.org2 |--- msp |--- tls
可以看出,每个 org 都包含了 msp、 ca、 tlsca 和 users 目录,然后根据 org 类型的不同,还分别有 peers 和 orderers 目录,里面存放着 org 中每个成员的 msp 和 tls 文件。
B. configtx.yaml configtxgen 工具根据该文件生成 Orderer 初始化的时候要使用的 genesis.block(创世块),获知 organization 的各种信息。因此,用户要根据 crypto-config.yaml 中关于 organization 的定义来修改 configtx.yaml 以生成合适的 genesis.block 。例如,用户在 crypto-config.yaml 中增加了一个 Org3 ,并且要创建一个包含 Org1, Org2, Org3 的集群,则应该通过以下两步修改 configtx.yaml :
在 profile 中增加 Org3:
在 Organization 中增加 Org3 的 MSPDir
注意的是每个 organization 中的 MSPDir 的值必须是这种形式:
crypto-config/{OrgType}/{OrgName}/msp
模板文件 在 Kubernetes 中部署 Fabric 时,需要为每个节点编写相应的配置文件。由于节点数可能很多,这是既复杂又易错的重复劳动。为提高效率,可通过模板自动生成配置文件。本文使用了 5 个模板文件,可用脚本替换其中的变量,均在笔者给出示例代码中的 templates 目录中,这些模板的作用如下:
A. namespace.yaml 定义 Fabric 集群在 K8s 中的 namespace ,它对应着 organization 的域名。为了在多节点共享证书等文件,使用了 NFS 服务器作为存储。在 K8s 中通过相应的 PV 和 PVC ,namespace 下的 Pod 可以通过 PVC 来获取与之相应的文件。
B. cli.yaml CLI pod 模板,每个 organization 中都配备了一个 CLI pod,目的是提供命令行界面,可统一管理组织内的所有 peer ,其中包括 channel 的创建, chaincode 的安装等。CLI Pod 的 CORE_PEER_ADDRESS 环境变量默认值为 org 中的第一个 peer,可以通过修改该环境变量来连接不同的 peer 。
yaml 文件中的 command 是为了防止 CLI pod 自动退出,CLI 的默认工作目录为 /opt/gopath/src/github.com/hyperledger/fabric/peer 。由于该目录下的 channel-artifacts 挂载了 NFS 上 /opt/share/channel-artifacts,因此把创建 channel 时返回的 xxx.block 文件放在该目录下供所有 CLI Pod共享。
C. ca.yaml Fabric 的 CA 服务的 pod 定义模板,用于 organization 中的证书管理,其 yaml 文件除了定义 deployment 外,还定义了 service 。service 通过 selector 与 deployment 绑定,其中 deployment 中的 label 是 selector 与其绑定的根据。
D. orderer.yaml Orderer 的 pod 定义模板,需要注意的是,cryptogen 并不会生成 genesis.block ,然而缺少该文件时,orderer 会启动失败,因此在启动 orderer 之前需要预先生成 genesis.block ,并将其放在相应的 org 目录下。
E. peer.yaml 每个 peer pod 的定义模板。在该 yaml 中分别定义了 peer 和 couchDB 两个 container 。在实例化 chaincode (cc) 时,peer 需要连接 Docker 引擎来创建 cc 容器,因此要把 worker 宿主机的 var/run/docker.sock 映射到 peer 容器内部。
DNS 在 Fabric 设计中, chaincode 目前是以 Docker 容器的方式运行在 peer 容器所在的宿主机上,peer 容器需要调用 Docker 引擎的接口来构建和创建 chaincode 容器,调用接口是通过这个连接:
unix:///var/run/docker.sock
通过 docker.sock 创建的容器脱离在 Kubernetes 的体系之外,虽然它仍在 Flannel 的网络上,但却无法获得 peer 节点的 IP 地址。这是因为创建该容器的 Docker 引擎使用宿主机默认的 DNS 解析来 peer 的域名,所以无法找到。
为了解决解析域名的问题,需要在每个 worker 的 DOCKER_OPTS 中加入相关参数,我的 kube-dns 的 IP 为10.68.0.2,宿主机网络 DNS 的 IP 地址假设为 10.4.246.1,为使得 chaincode 的容器可以解析到 peer 节点,在每个 Docker 节点,修改步骤如下:
1 2 3 # echo 'DOCKER_OPTS="--dns=10.68.0.2 --dns=10.4.246.1 --dns-search default.svc.cluster.local --dns-search svc.cluster.local --dns-opt ndots:2 --dns-opt timeout:2 --dns-opt attempts:2"' >> /etc/default/docker# echo 'EnvironmentFile=-/etc/default/docker' >> /etc/systemd/system/docker.service# systemctl daemon-reload && systemctl restart docker && systemctl status docker
部署 以下操作都在 CMD 客户机上进行,NFS 的共享目录为 /opt/share。
在 CMD 中挂载 NFS 目录 在 nfs server 上创建 /data 和 /opt/share 两个目录,保证 CMD 和集群可以有权限读写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # cat << EOF >> /etc/exports /opt/share 10.4.249.231(rw,insecure,no_root_squash) /data 10.4.249.231(rw,insecure,no_root_squash) /data 172.31.21.0/24(rw,insecure,no_root_squash) /opt/share 172.31.21.0/24(rw,insecure,no_root_squash) EOF # exportfs -rv exporting 10.4.249.231:/data exporting 10.4.249.231:/opt/share exporting 172.31.21.0/24:/opt/share exporting 172.31.21.0/24:/data # showmount -e 172.31.21.208 Exports list on 172.31.21.208: /data 172.31.21.0/24 10.4.249.231 /opt/share 172.31.21.0/24 10.4.249.231
在 CMD 上创建 /opt/share 和 /opt/data 目录并 mount
1 2 3 4 5 # mkdir -p /opt/share && sudo chmod 777 /opt/share# mkdir -p /opt/data && sudo chmod 777 /opt/data# sudo mount -t nfs 172.31.21.208:/opt/share /opt/share# sudo mount -t nfs 172.31.21.208:/data /opt/data
下载源码和 Fabric 脚本 1 2 3 # git clone https://github.com/batizhao/fabric-on-kubernetes.git # cd fabric-on-kubernetes# wget https://nexus.hyperledger.org/content/repositories/releases/org/hyperledger/fabric/hyperledger-fabric/darwin-amd64-1.2.0/hyperledger-fabric-darwin-amd64-1.2.0.tar.gz && tar -zxvf hyperledger-fabric-darwin-amd64-1.2.0.tar.gz && rm -rf hyperledger-fabric-darwin-amd64-1.2.0.tar.gz && rm -rf config
这段会在当前目录下生成一个 bin 目录,包含了运行 Fabric 的二进制脚本和证书、创世块生成工具。
在部署之前,还需要修改 cli 和 namespace 中的 nfs 地址。
部署 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # ./generateALL.sh org1 org2 2018-09-12 14:01:38.669 CST [common/tools/configtxgen] main -> WARN 001 Omitting the channel ID for configtxgen is deprecated. Explicitly passing the channel ID will be required in the future, defaulting to 'testchainid'. 2018-09-12 14:01:38.669 CST [common/tools/configtxgen] main -> INFO 002 Loading configuration 2018-09-12 14:01:38.717 CST [msp] getMspConfig -> INFO 003 Loading NodeOUs 2018-09-12 14:01:38.719 CST [msp] getMspConfig -> INFO 004 Loading NodeOUs 2018-09-12 14:01:38.719 CST [common/tools/configtxgen] doOutputBlock -> INFO 005 Generating genesis block 2018-09-12 14:01:38.720 CST [common/tools/configtxgen] doOutputBlock -> INFO 006 Writing genesis block 2018-09-12 14:01:38.788 CST [common/tools/configtxgen] main -> INFO 001 Loading configuration 2018-09-12 14:01:38.812 CST [common/tools/configtxgen] doOutputChannelCreateTx -> INFO 002 Generating new channel configtx 2018-09-12 14:01:38.814 CST [msp] getMspConfig -> INFO 003 Loading NodeOUs 2018-09-12 14:01:38.816 CST [msp] getMspConfig -> INFO 004 Loading NodeOUs 2018-09-12 14:01:38.817 CST [common/tools/configtxgen] doOutputChannelCreateTx -> INFO 005 Writing new channel tx 2018-09-12 14:01:38.858 CST [common/tools/configtxgen] main -> INFO 001 Loading configuration 2018-09-12 14:01:38.879 CST [common/tools/configtxgen] doOutputAnchorPeersUpdate -> INFO 002 Generating anchor peer update 2018-09-12 14:01:38.881 CST [common/tools/configtxgen] doOutputAnchorPeersUpdate -> INFO 003 Writing anchor peer update 2018-09-12 14:01:38.934 CST [common/tools/configtxgen] main -> INFO 001 Loading configuration 2018-09-12 14:01:38.958 CST [common/tools/configtxgen] doOutputAnchorPeersUpdate -> INFO 002 Generating anchor peer update 2018-09-12 14:01:38.958 CST [common/tools/configtxgen] doOutputAnchorPeersUpdate -> INFO 003 Writing anchor peer update
这段调用 configtxgen 生成各组织的证书,调用 python 生成 Kubernetes 的 yaml 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # python3 transform/run.py namespace "orgorderer1" created persistentvolume "orgorderer1-pv" created persistentvolumeclaim "orgorderer1-pv" created deployment "orderer0-orgorderer1" created service "orderer0" created namespace "org2" created persistentvolume "org2-pv" created persistentvolumeclaim "org2-pv" created deployment "ca" created service "ca" created persistentvolume "org2-artifacts-pv" created persistentvolumeclaim "org2-artifacts-pv" created deployment "cli" created deployment "peer0-org2" created service "peer0" created deployment "peer1-org2" created service "peer1" created namespace "org1" created persistentvolume "org1-pv" created persistentvolumeclaim "org1-pv" created deployment "ca" created service "ca" created persistentvolume "org1-artifacts-pv" created persistentvolumeclaim "org1-artifacts-pv" created deployment "cli" created deployment "peer0-org1" created service "peer0" created deployment "peer1-org1" created service "peer1" created
这段会通过 python 脚本调用 kubectl 在 Kubernetes 上创建 Fabric 集群。
验证 1 2 3 4 5 6 7 8 9 10 11 # kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE org1 ca-744f5bfdbb-nb5pj 1/1 Running 0 4m org1 cli-59d46f884-p5grk 1/1 Running 0 4m org1 peer0-org1-6f8bd58fc8-dfvxp 2/2 Running 1 4m org1 peer1-org1-554b6d8fb-kjrqj 2/2 Running 0 4m org2 ca-6bd89dbc8d-zdgnb 1/1 Running 1 4m org2 cli-7798868bf9-htkk2 1/1 Running 0 4m org2 peer0-org2-f9fb8b694-7qhm8 2/2 Running 0 4m org2 peer1-org2-7b9854f9fb-xmps4 2/2 Running 0 4m orgorderer1 orderer0-orgorderer1-df4769577-mtzmp 1/1 Running 0 4m
几分钟以后,可以看到所有 Pod 都 Running 了。
测试 Fabric 集群 当所有 Pod Running 之后,可以进入开发、部署 chaincode 的阶段。
进入 CLI 容器 A. 查找 org1 下边的容器 1 2 3 4 5 6 # kubectl get pod -n org1 NAME READY STATUS RESTARTS AGE ca-744f5bfdbb-nb5pj 1/1 Running 0 2d cli-59d46f884-p5grk 1/1 Running 0 2d peer0-org1-6f8bd58fc8-dfvxp 2/2 Running 1 2d peer1-org1-554b6d8fb-kjrqj 2/2 Running 0 2d
登录 org1 cli 容器
1 2 # kubectl exec -it cli-59d46f884-p5grk bash -n org1 root@cli-59d46f884-p5grk:/opt/gopath/src/github.com/hyperledger/fabric/peer#
创建channel
1 2 3 # peer channel create -o orderer0.orgorderer1:7050 -c mychannel -f ./channel-artifacts/channel.tx ... 2018-08-13 11:00:22.265 UTC [cli/common] readBlock -> INFO 05e Received block: 0
拷贝 mychannel.block 到 channel-artifacts 目录
1 # cp mychannel.block channel-artifacts
加入 mychannel
1 2 3 # peer channel join -b channel-artifacts/mychannel.block ... 2018-08-13 11:02:54.961 UTC [channelCmd] executeJoin -> INFO 041 Successfully submitted proposal to join channel
更新 anchor peer,每个 org 只需执行一次
1 2 3 # peer channel update -o orderer0.orgorderer1:7050 -c mychannel -f./channel-artifacts/Org1MSPanchors.tx ... 2018-08-13 11:03:47.907 UTC [channelCmd] update -> INFO 04d Successfully submitted channel update
B. 查找 org2 下边的容器 1 2 3 4 5 6 # kubectl get pod -n org2 NAME READY STATUS RESTARTS ca-8665cf9b9b-v5m8p 1/1 Running 0 cli-7798868bf9-zmq8d 1/1 Running 1 peer0-org2-f9fb8b694-mw54z 2/2 Running 0 peer1-org2-7b9854f9fb-t5plg 2/2 Running 0
登录 org2 cli 容器
1 2 # kubectl exec -it cli-7798868bf9-zmq8d bash -n org2 root@cli-7798868bf9-zmq8d:/opt/gopath/src/github.com/hyperledger/fabric/peer#
加入 mychannel
1 2 3 # peer channel join -b channel-artifacts/mychannel.block ... 2018-08-13 11:02:54.961 UTC [channelCmd] executeJoin -> INFO 041 Successfully submitted proposal to join channel
更新 anchor peer,每个 org 只需执行一次
1 2 3 # peer channel update -o orderer0.orgorderer1:7050 -c mychannel -f./channel-artifacts/Org2MSPanchors.tx ... 2018-08-13 11:06:48.166 UTC [channelCmd] update -> INFO 04d Successfully submitted channel update
安装 chaincode 分别登录 org1 cli ,org2 cli 容器
1 2 3 4 5 # kubectl exec -it cli-59d46f884-p5grk bash -n org1 root@cli-59d46f884-p5grk:/opt/gopath/src/github.com/hyperledger/fabric/peer# # kubectl exec -it cli-7798868bf9-zmq8d bash -n org2 root@cli-7798868bf9-zmq8d:/opt/gopath/src/github.com/hyperledger/fabric/peer#
安装 chaincode
1 2 3 # peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/peer/channel-artifacts/chaincode ... 2018-08-13 13:29:05.614 UTC [chaincodeCmd] install -> INFO 050 Installed remotely response:<status:200 payload:"OK" >
实例化 chaincode(只要在任意 org 执行一次)
1 2 3 4 5 6 7 8 9 10 11 12 # peer chaincode instantiate -o orderer0.orgorderer1:7050 \ -C mychannel -n mycc -v 1.0 \ -c '{"Args":["init","a", "100", "b","200"]}' \ -P "OR ('Org1MSP.peer','Org2MSP.peer')" ... 2018-08-13 13:39:28.069 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 04a Using default escc 2018-08-13 13:39:28.069 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 04b Using default vscc 2018-08-13 13:39:28.069 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 04c java chaincode disabled 2018-08-13 13:39:28.069 UTC [msp/identity] Sign -> DEBU 04d Sign: plaintext: 0A8D070A6608031A0B089094C6DB0510...535010030A04657363630A0476736363 2018-08-13 13:39:28.069 UTC [msp/identity] Sign -> DEBU 04e Sign: digest: F5EB7F5FEB4C3CE151402B3A43E285BA2FD7B0FA6FBC355376417407BC9CAC27 2018-08-13 13:39:34.045 UTC [msp/identity] Sign -> DEBU 04f Sign: plaintext: 0A8D070A6608031A0B089094C6DB0510...394757C502AA08930A47A8BF4BA9263D 2018-08-13 13:39:34.045 UTC [msp/identity] Sign -> DEBU 050 Sign: digest: F55A876EC071C16D5FE5CEB6A155F075B65A9EBAEFD26B5D916D10921A25D6A8
查询账本
1 2 # peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}' 100
a to b 转帐 10
1 2 3 4 5 6 # peer chaincode invoke -o orderer0.orgorderer1:7050\ -C mychannel -n mycc \ --peerAddresses peer0.org1:7051 \ -c '{"Args":["invoke","a","b","10"]}' ... 2018-08-13 13:54:46.298 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 050 Chaincode invoke successful. result: status:200
查询账本
1 2 3 4 5 # peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}' 90 # peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}' 210
Chaincode Java Client 这里 用 Java 实现了一个 Chaincode API 调用,效果同上边的命令行。
清除集群 当需要删除集群的时候,可以通过 transform 目录下的 delete.py 脚本来清理环境,该脚本会遍历 crypto-config 目录,找出所有的 yaml 文件,并通过 kuberclt delete -f xxx.yaml 的方式将资源逐个删除。
1 2 # python3 transform/delete.py # sudo rm -rf /opt/data/{orderer,peer}
总结 本文介绍的部署方法,是基于 Kubernetes 容器云平台实现 BaaS 的基础步骤。先介绍了 Fabric 的架构、详细部署过程,之后介绍了 chaincode 的部署和调用。在此之上,可以增加更多的区块链层管理功能,图形化运维界面,使得开发人员投入更多的精力到应用的业务逻辑上。
在此之前,有试用过 Heperledger Cello ,发现这个项目还没法用。在网上找到 How to Deploy Hyperledger Fabric on Kubernetes 这篇博文,和国内这篇 用Kubernetes部署超级账本Fabric的区块链即服务 做参考,应该是同一个人写的。只不过写于 2017 年 Fabric v1.0 的时候,现在已经是 v1.2,所以对项目做了些修改才能运行。这应该也是目前唯一的 Fabric v1.2 on Kubernetes 部署指引。后续会通过实现一些 chaincode 来更深入的理解 Fabric 原理。