在 Kubernetes 上部署 Hyperledger Fabric v1.2 Solo(二)

在上一篇文章中,我们介绍了在 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 的类型,两种组织的内部结构如下:

1) OrdererOrgs 中定义了一个名字为 Orderer ,域名为 orgorderer1 的 org 。

2) 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:

图1

在 Organization 中增加 Org3 的 MSPDir

图2

注意的是每个 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 原理。