前情提要
说明
前面看了user guide,主要是对CA服务器和CA客户端的说明,包括但不限于命令行。这次主要是操作指南,这个说的是在已经知道了CA服务器如何使用的情况下,和Fabric如何结合起来,也就是为fabric中的各个节点提供加密材料,证书啊、私钥啊之类的。
在本指南中,您将看到建立包括两个组织的区块链网络的过程,每个组织都有2个节点,还有一个组织是1个排序节点。 将看到如何为排序节点(orderer),普通节点(peer),管理员(administrator)和最终用户(end user)生成加密材料,以使私钥永远不会离开生成它们的主机或容器(就是说本机生成,永远存在本机,不会通过网络传输)。
这个教程感觉才是正经的搭建网络,test-network那个更像是尝鲜,其他fabric例子都是拓展智能合约和应用层功能,这个才是真正的教你一步步搭建网络,然后再接上链码的事儿就顺了。
大纲
(目录没用,请用侧边栏自带的目录)
拓扑
演示在3个组织间设置排序节点、普通节点和CA。下图
这里有一个各组织共用的TLS CA节点,3个组织,每个组织有不同的情况,但大致包括多个主机,各个组织自己的CA,以及各个组织自己的节点。2个组织是普通节点,1个组织是排序节点。理论上是9台电脑。
其实可以按这个图想一下步骤:首先创建一个TLS CA,它默认有一个admin用户(就是-b指定的那个),为admin颁发一个证书(enroll)。接下来,TLS CA管理员要通过自己的操作来为各个节点注册登记(register,换句话说就是不开放注册),以便知道后期都有谁来申请证书(共5个,4个peer,1个orderer)。其次排序节点的CA,先产生CA MSP,然后管理员颁发凭证CA Admin MSP,再次是各个组织的CA节点,此处有2个,步骤同上,先CA MSP, 再CA Admin MSP。排序节点和普通节点还有要注册相关用户的操作。
第三,对于每一个组织,普通节点准备好从本组织的CA申请的Local peer MSP和从TLS CA申请的TLS CA MSP,这就能启动了
其他地方看到一句话:访问Fabric CA涉及到两个流程。登记(enrollment)指的是用户从指定CA请求并获取数字证书,注册(registration)通常由注册员完成,他负责告诉CA签发数字证书。
但是我喜欢管rigistration叫注册登记(因为有用户名密码),enrollment叫颁发证书。
给了不同的docker-compose文件,可以配置不同类型的节点(一个指向docker-compose的链接,是完整的各组织文件,也就是多个服务放在一块了 )。
还是先创建testop的空项目,创建一个docker-compose.yml文件,下面一点一点拷贝集成。
初始化CA
还是先搞CA。
TLS CA
第一步,先下载一个fabric-ca-client,这在fabric-sample下载时,都已经有了,没有的去找两篇教程看看。
例子中说用的是1.4.0版本的CA。不过我感觉用镜像也无所谓,用不到。
第二步,初始化TLS的CA,用来生成TLS通信要用的CA。这个例子所有组织用相同的TLS,并且禁用TLS相互验证,为了简便。。。(实际是,每个组织的TLS CA应当从组织CA上派生出来)
接下来给了一个TLS CA的示例yml文件
ca-tls:
container_name: ca-tls
image: hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052'
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- /tmp/hyperledger/tls/ca:/tmp/hyperledger/fabric-ca
networks:
- fabric-ca # 这里是弄一个网络,让各个容器加入进来
ports:
- 7052:7052
用docker-compose up ca-tls创建并启动容器,但是实测用下面这个可以创建网络并加入(其实是从一开始给的那个整体的yml文件上改的)
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
ports:
- 7052:7052 # 监听7052
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls/ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052,-d代表输出debug信息
networks: # 然后在这里指定才行
- fabric-ca
实际产生的网络名是tlsca_fabric-ca。可以用docker network ls命令查看
docker network inspect [network]还能检查网络中有哪些容器
docker network rm [network]可以删掉网络
此时,TLS CA服务器正在安全套接字上进行侦听,并且可以开始颁发TLS证书。(其实就是个CA,逻辑上叫做TLS CA了)
把/tmp/hyperledger/fabric-ca/crypto/ca-cert.pem文件复制到将要运行CA客户端的主机上。 这个证书也被称为TLS CA的签名证书,可用来验证CA的TLS证书。当把这个证书拷贝到CA客户端的机器上,你就可以用CA发命令。每台主机上都应该有这个证书。(感觉是把证书生成好之后,拿去给相应主机用)
按照教程所说,这里应该是在客户端主机(比如在我的机器上),运行下面的注册身份命令
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/tls-ca/crypto/tls-ca-cert.pem
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/tls-ca/admin
fabric-ca-client enroll -d -u https://tls-ca-admin:tls-ca-adminpw@0.0.0.0:7052
fabric-ca-client register -d --id.name peer1-org1 --id.secret peer1PW --id.type peer -u https://0.0.0.0:7052
fabric-ca-client register -d --id.name peer2-org1 --id.secret peer2PW --id.type peer -u https://0.0.0.0:7052
fabric-ca-client register -d --id.name peer1-org2 --id.secret peer1PW --id.type peer -u https://0.0.0.0:7052
fabric-ca-client register -d --id.name peer2-org2 --id.secret peer2PW --id.type peer -u https://0.0.0.0:7052
fabric-ca-client register -d --id.name orderer1-org0 --id.secret ordererPW --id.type orderer -u https://0.0.0.0:7052
把ca-cert.pem拷贝成tls-ca-cert.pem。
这里这些操作都是由tls-ca的管理员做的
第一个颁发是为tls-ca-admin弄了一个凭证(证书和私钥),这个账户就是TLS CA的管理员tls-ca-admin:tls-ca-adminpw。接下来需要TLS CA的管理员对几个身份进行注册登记。
但是我这里发现,还是在容器内好使,那就进入到容器docker exec -it ca-tls /bin/bash,执行上面那一堆,注意相关文件路径,改成容器内的!
弄完然后查询一下身份,可以发现多出来已登记的身份
fabric-ca-client identity list
接下来我弄一下其他节点。
排序(orderer)节点CA
这是第2个CA容器了。
同样从CA开始,用于orderer的host内部的加密材料来源。给了一段docker-compose配置,还是和上面那个合并起来,反正会产生2个服务(2个容器)。
rca-org0:
container_name: rca-org0
image: hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053'
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- /tmp/hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
networks:
- fabric-ca
ports:
- 7053:7053
和之前那个合并起来,做一些修改
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca # 容器内还是映射到tmp里,外面这里就分一下文件夹就行
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053'#这里的用户名其实我觉得真的部署也可以直接写admin
OK,启动起来(docker-compose up,需要后台运行加上-d),查一下
项目结构现在这样:
继续,为这个CA的admin颁发凭证,然后登记注册账户。这个是在orderer的CA容器中运行,还是和TLS CA时候操作一样
docker exec -it rca-org0 /bin/bash
注意修改成容器内路径,这个是教程里复制来的
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/org0/ca/crypto/ca-cert.pem
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/org0/ca/admin
fabric-ca-client enroll -d -u https://rca-org0-admin:rca-org0-adminpw@0.0.0.0:7053
fabric-ca-client register -d --id.name orderer1-org0 --id.secret ordererpw --id.type orderer -u https://0.0.0.0:7053
fabric-ca-client register -d --id.name admin-org0 --id.secret org0adminpw --id.type admin --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*,hf.Revoker=true,hf.GenCRL=true,admin=true:ecert,abac.init=true:ecert" -u https://0.0.0.0:7053
这里发现登记注册了两个账户,orderer1-org0和admin-org0,可能是一起用来管理orderer节点吧。
组织Org1的CA
这是第3个CA了。
玩法一样,复制过来稍微改改,直接贴结果
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053' #这里的用户名其实我觉得真的部署也可以直接写admin
# 组织1的CA
rca-org1:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org1
networks:
- fabric-ca
ports:
- 7054:7054
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org1
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org1-admin:rca-org1-adminpw'
重新启动一下docker-compose up。得到结果。
按照教程所说,登记注册4个账户(然后总共就有了5个身份,算上rca-org1-admin):
- Peer 1 (peer1-org1)
- Peer 2 (peer2-org1)
- Admin (admin1-org1)
- End user (user-org1)
和orderer节点的操作一样的,我这里依然在容器里弄了,不考虑拷贝出来的情况(教程里是把我们的宿主机当做好几台电脑了)。
docker exec -it rca-org1 /bin/bash
注意修改成容器内路径,这个是教程里复制来的
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/org1/ca/crypto/ca-cert.pem
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/org1/ca/admin
fabric-ca-client enroll -d -u https://rca-org1-admin:rca-org1-adminpw@0.0.0.0:7054
fabric-ca-client register -d --id.name peer1-org1 --id.secret peer1PW --id.type peer -u https://0.0.0.0:7054
fabric-ca-client register -d --id.name peer2-org1 --id.secret peer2PW --id.type peer -u https://0.0.0.0:7054
fabric-ca-client register -d --id.name admin-org1 --id.secret org1AdminPW --id.type user -u https://0.0.0.0:7054
fabric-ca-client register -d --id.name user-org1 --id.secret org1UserPW --id.type user -u https://0.0.0.0:7054
处理好之后,看到如下的文件结构,看到2个组织和tls的文件夹,分别都有了CA MSP和CA admin MSP:
组织Org2的CA
同上,贴结果
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053' #这里的用户名其实我觉得真的部署也可以直接写admin
# 组织1的CA
rca-org1:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org1
networks:
- fabric-ca
ports:
- 7054:7054
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org1
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org1-admin:rca-org1-adminpw' # 这里没有指定端口7054,因为默认就是7054
# 组织2的CA
rca-org2:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org2
networks:
- fabric-ca
ports:
- 7055:7055
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org2
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org2/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org2-admin:rca-org2-adminpw --port 7055'
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/org2/ca/crypto/ca-cert.pem
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/org2/ca/admin
fabric-ca-client enroll -d -u https://rca-org2-admin:rca-org2-adminpw@0.0.0.0:7055
fabric-ca-client register -d --id.name peer1-org2 --id.secret peer1PW --id.type peer -u https://0.0.0.0:7055
fabric-ca-client register -d --id.name peer2-org2 --id.secret peer2PW --id.type peer -u https://0.0.0.0:7055
fabric-ca-client register -d --id.name admin-org2 --id.secret org2AdminPW --id.type user -u https://0.0.0.0:7055
fabric-ca-client register -d --id.name user-org2 --id.secret org2UserPW --id.type user -u https://0.0.0.0:7055
OK,现在越来越复杂了。不过终于弄完了CA节点,一共4个CA,TLS,3个组织各一。
初始化Peer
在rca-org1上操作。Org1的管理员在CA上注册peer节点,然后用准备好的加密材料启动peer节点。
这里要准备的是这样几个材料,Local Peer MSP, TLS CA MSP,对peer1还有一个Org1 Admin MSP。
Local Peer MSP是向本组织的CA申请的(下图的host1),TLS CA MSP是向TLS CA申请的(下图没有),Org1 Admin MSP是管理员身份材料,
org1的peer1
第1个证书,ORG1 CA的。
做了这么一个映射,这样等下弄的文件还会在宿主机上,不然容器一停就没了。
#这是给rca-org1添加的,后面peer2,admin也得添加,这样保存下来
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org1/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
做一个拷贝,之前org1的CA(rca-org1的CA MSP里的证书),ca-cert.pem
mkdir -p /tmp/hyperledger/peer1/assets/ca
cp /tmp/hyperledger/fabric-ca/crypto/ca-cert.pem /tmp/hyperledger/peer1/assets/ca/org1-ca-cert.pem
然后运行这个,得到peer1的msp(Local Peer MSP)
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/peer1
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/peer1/assets/ca/org1-ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://peer1-org1:peer1PW@0.0.0.0:7054
继续。
第2个证书,TLS CA的。
还是得拷贝过来(ca-tls的CA MSP里的证书)
/tmp/hyperledger/org1/peer1/assets/tls-ca/tls-ca-cert.pem,反正都是本地到docker的映射,直接拷贝过来吧
图里msp文件似乎因为权限问题看不到,但里面已经有了刚才生成的peer1的msp。然后是assets中的两个ca证书
按说应该执行下面,指定tls-msp的目录,准备盛放tls的msp。
export FABRIC_CA_CLIENT_MSPDIR=tls-msp
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/org1/peer1/assets/tls-ca/tls-ca-cert.pem
fabric-ca-client enroll -d -u https://peer1-org1:peer1PW@0.0.0.0:7052 --enrollment.profile tls --csr.hosts peer1-org1
OK,好头疼,就上面这几句,从TLS CA再获取一次凭证
这里总之就是为peer1节点的镜像做准备,这一步应该是Org1的CA管理员做的,为peer1准备加密材料。这里我在rca-org1中运行的时候,出问题,说是连接不到7052,先不管了。我在本机想办法运行了一下,指定上面那两个变量时候用绝对路径。这样就可以颁发凭证,产生下面的结果,还是因为权限,看不到tls-msp里面的东西。
下面这个图虽然是peer2的,但是不要紧,这是我在本机root权限下找到fabric-ca-client,从TLS CA获得凭证的3条命令
运行之后就是下面的文件结构,出现tls-msp(这就是peer1的TLS CA MSP)
然后去org1/peer1/tls-msp/keystore里给那个文件改名字,改成key.pem
org1的peer2
同上,再来一遍。
先拷贝,再从本CA产生一个msp,再从TLS CA产生一个tls-msp。完事,别忘记改名字。
OK,这次快很多,看看文件结构(真是够复杂的)。
颁发凭证给org1的admin
管理员身份是负责安装和实例化链码的。
下面假定在peer1的主机上运行(我还是在rca-org1内运行的)。
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/admin
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/peer1/assets/ca/org1-ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://admin-org1:org1AdminPW@0.0.0.0:7054
然后产生了msp(Org1 Admin MSP)。
在peer1/msp下创建admincerts,这就是admin的凭证了
mkdir /tmp/hyperledger/peer1/msp/admincerts
cp /tmp/hyperledger/admin/msp/signcerts/cert.pem /tmp/hyperledger/peer1/msp/admincerts/org1-admin-cert.pem
在peer2下也要创建同样的文件夹,拷贝过去admin的证书。
到这里总算是准备好了一个节点的3个MSP。
启动org1的节点
启动成功,上yml
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA 7052
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA 7053
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053' #这里的用户名其实我觉得真的部署也可以直接写admin
# 组织1的CA 7054
rca-org1:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org1
networks:
- fabric-ca
ports:
- 7054:7054
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org1
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org1/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
- ./hyperledger/org1/peer2:/tmp/hyperledger/peer2
- ./hyperledger/org1/admin:/tmp/hyperledger/admin
# - ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1 # 临时用一下,然后再去掉
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org1-admin:rca-org1-adminpw' # 这里没有指定端口7054,因为默认就是7054
# 组织2的CA 7055
rca-org2:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org2
networks:
- fabric-ca
ports:
- 7055:7055
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org2
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org2/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org2-admin:rca-org2-adminpw --port 7055'
# org1的peer1节点 7051
peer1-org1:
image: hyperledger/fabric-peer:2.2
container_name: peer1-org1
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer1-org1
- CORE_PEER_ADDRESS=peer1-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer1/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=guide_fabric-ca #这个网的名字不知道要不要改,等一下测试看看
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true # 这几个TLS路径都是前面生成的
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org1/peer1/tls-msp/keystore/key.pem # 这个key.pem是我们刚刚改过的名字
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1-org1:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1/peer1
volumes:
- /var/run:/host/var/run
- ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1 # 这里和rca-org1使用了同样的路径,不知道会不会影响,实际按说不应当这样做,也不管了,先运行起来看看
翻了半天(几个服务器一块启动,输出信息全在这),找到了教程里提到的成功这句。
对于peer2也是一样,要把admin的那个证书放进去,才能启动镜像。就是上一节结尾,对peer2也操作一遍。
测试了一下,peer镜像里面没有fabric-ca-client命令,有peer命令
准备Org2的节点
可以说是同上,只不过要在rca-org2中运行了。
现在我们扮演Org2的管理员,在ORG2 CA上注册peer节点,然后用准备好的加密材料启动peer节点。
还记得步骤么?有点多。
- 准备好rca-org2容器的映射,这样等下可以看到存在宿主机的加密材料。这回自动创建peer1,peer2之类的文件夹。
- 拷贝ORG2 CA的证书到各个节点中,peer1/assets/ca/org2-ca-cert.pem
- 执行相应操作,生成一个节点的msp
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/peer1
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/peer1/assets/ca/org2-ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://peer1-org2:peer1PW@0.0.0.0:7055
- 拷贝TLS CA的证书到各个节点,peer1/assets/tls-ca/tls-ca-cert.pem(其实和org1的一样,真实场景应该是不一样的)
- 执行相应操作,生成一个节点的tls-msp
- 别忘了org2的admin,生成之后还要拷贝到节点的msp/admincerts,具体命令都在上面了,注意修改命令中的信息。
- 准备节点容器,停了之后,重新运行起来(正常情况下,不需要,我这里写在一起,所以需要这么做一下)
搞定,现在有8个容器了,看看yml怎么样了。
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA 7052
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA 7053
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053' #这里的用户名其实我觉得真的部署也可以直接写admin
# 组织1的CA 7054
rca-org1:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org1
networks:
- fabric-ca
ports:
- 7054:7054
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org1
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org1/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
- ./hyperledger/org1/peer2:/tmp/hyperledger/peer2
- ./hyperledger/org1/admin:/tmp/hyperledger/admin
# - ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1 # 临时用一下,然后再去掉
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org1-admin:rca-org1-adminpw' # 这里没有指定端口7054,因为默认就是7054
# 组织2的CA 7055
rca-org2:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org2
networks:
- fabric-ca
ports:
- 7055:7055
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org2
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org2/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org2/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
- ./hyperledger/org2/peer2:/tmp/hyperledger/peer2
- ./hyperledger/org2/admin:/tmp/hyperledger/admin
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org2-admin:rca-org2-adminpw --port 7055'
# org1的peer1节点 7051
peer1-org1:
image: hyperledger/fabric-peer:2.2
container_name: peer1-org1
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer1-org1
- CORE_PEER_ADDRESS=peer1-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer1/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org1/peer1/tls-msp/keystore/key.pem # 这个key.pem是我们刚刚改过的名字
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1-org1:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1/peer1
volumes:
- /var/run:/host/var/run
- ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1
# org1的peer2节点 7051
peer2-org1:
container_name: peer2-org1
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer2-org1
- CORE_PEER_ADDRESS=peer2-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer2/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=grpc=debug:info
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org1/peer2/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org1/peer2/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer2/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer2-org1:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
- CORE_PEER_GOSSIP_BOOTSTRAP=peer1-org1:7051
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1/peer2
volumes:
- /var/run:/host/var/run
- ./hyperledger/org1/peer2:/tmp/hyperledger/org1/peer2
# org2的peer1节点 7051
peer1-org2:
image: hyperledger/fabric-peer:2.2
container_name: peer1-org2
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer1-org2
- CORE_PEER_ADDRESS=peer1-org2:7051
- CORE_PEER_LOCALMSPID=org2MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org2/peer1/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org2/peer1/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org2/peer1/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org2/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1-org2:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org2/peer1
volumes:
- /var/run:/host/var/run
- ./hyperledger/org2/peer1:/tmp/hyperledger/org2/peer1
# org2的peer2节点 7051
peer2-org2:
image: hyperledger/fabric-peer:2.2
container_name: peer2-org2
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer2-org2
- CORE_PEER_ADDRESS=peer2-org2:7051
- CORE_PEER_LOCALMSPID=org2MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org2/peer2/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org2/peer2/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org2/peer2/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org2/peer2/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer2-org2:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
- CORE_PEER_GOSSIP_BOOTSTRAP=peer1-org2:7051
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org2/peer2
volumes:
- /var/run:/host/var/run
- ./hyperledger/org2/peer2:/tmp/hyperledger/org2/peer2
都在运行了,一共8个容器,还差最后一步,就搭建完成了!
初始化Orderer
这里比较简单了,前面已经处理过CA相关了。
回想一下,orderer CA一共注册了几个身份(账户)?
只有orderer1-org0和admin-org0两个。
那么接下来,应该有感觉了,就是颁发凭证。
颁发orderer的凭证
没有fabric-ca-client先去下载一个(我这里还是等下直接去org0里弄,反正映射之后也能搭建起来)
先local orderer MSP。
还是一样,先拷贝org0的ca,
mkdir -p /tmp/hyperledger/orderer/assets/ca
cp /tmp/hyperledger/fabric-ca/crypto/ca-cert.pem /tmp/hyperledger/orderer/assets/ca/org0-ca-cert.pem
export FABRIC_CA_CLIENT_HOME=/tmp/hyperledger/orderer
export FABRIC_CA_CLIENT_TLS_CERTFILES=/tmp/hyperledger/orderer/assets/ca/org0-ca-cert.pem
fabric-ca-client enroll -d -u https://orderer1-org0:ordererpw@0.0.0.0:7053
得到msp。
tls一样,从前面的节点拷贝一个过来tls-ca
进行tls的颁发凭证。
然后改名。
再org0 admin MSP。
过程类似(到这里发现,admin需要的就是org的证书,指定一个文件夹,然后就能生成相应凭证,所以其实可以在普通节点之前,我觉得从逻辑上来说应该在前。也就是先为admin颁发,再为其他账户颁发)。
别忘记把admin的拷贝到orderer节点的msp/admincert/orderer-admin-cert.pem
好了,退出rca-org0吧。。。
现在准备启动orderer
创建创世区块和通道事务
给了一个更详细的链接
接下来要编辑configtx.yaml了(好像是orderer节点的配置文件),给了一个完整的模板
先在项目根目录创建一个configtx.yaml,等下放到正确的地方。
在orderer节点,我们需要收集所有组织的MSP。所以需要这样写:
Organizations:
- &org0
Name: org0
ID: org0MSP
MSPDir: ./orderer/org0/msp
- &org1
Name: org1
ID: org1MSP
MSPDir: ./orderer/org1/msp
AnchorPeers:
- Host: peer1-org1
Port: 7051
- &org2
Name: org2
ID: org2MSP
MSPDir: ./orderer/org2/msp
AnchorPeers:
- Host: peer1-org2
Port: 7051
看来这里重新组织一个文件夹比较好。
/tmp/hyperledger/org0/msp
├── admincerts
│ └── admin-org0-cert.pem
├── cacerts
│ └── org0-ca-cert.pem
├── tlscacerts
│ └── tls-ca-cert.pem
└── users
/tmp/hyperledger/org1/msp
├── admincerts
│ └── admin-org1-cert.pem
├── cacerts
│ └── org1-ca-cert.pem
├── tlscacerts
│ └── tls-ca-cert.pem
└── users
/tmp/hyperledger/org2/msp
├── admincerts
│ └── admin-org2-cert.pem
├── cacerts
│ └── org2-ca-cert.pem
├── tlscacerts
│ └── tls-ca-cert.pem
└── users
在项目中我创建了orderer文件夹,里面放org0,org1,org2这三个,里面如上所示。
比如,org1,找peer1里面就都有了,msp/admincert里是第一个,assets/ca里是第二个,assets/tls-ca里是第三个。
结果如下:
接下来给了这么两个命令,在configtx.yaml所在目录执行,我还是把这个文件放在根目录下
configtxgen -profile OrgsOrdererGenesis -outputBlock ./orderer/org0/orderer/genesis.block -channelID syschannel
configtxgen -profile OrgsChannel -outputCreateChannelTx ./orderer/org0/orderer/channel.tx -channelID mychannel
这里需要本机上装过了上面这些命令,就是fabric当时下载就有的。搜两个教程看看。
这路径需要改
这个yaml不知道怎么出了问题,明天来看一下。
2021-4-23
现在继续,看一下configtx.yaml为什么格式不对。再看看VSCode的自动填充空格。
似乎是识别不了<<,但是模板里也有啊。
先看看命令的含义。
profile,outputBlock,channelID,outputCreateChannelTx。4个。configtxgen的命令说明说主要根据yaml文件生成,要参考全部配置去看例子里那个config/configtx.yaml,但是有点迷啊。
profile,是生成所需参考的部分?破案了
################################################################################
#
# Profile
#
# - Different configuration profiles may be encoded here to be specified
# as parameters to the configtxgen tool
# 可能会有不同的profile配置,作为参数传递给configtxgen工具
#
################################################################################
outputBlock,用于写出创世区块的路径
channelID,通道名。这个命令本身就是通道相关设置,但是创世区块为什么需要一个通道?这里有回答
因为共享配置,然后配一个配置作为一个事务,各有一个通道。并且每一个配置事务还在configtx中有一个名字。
通道配置(就这么称呼了)有3个特点:
- 都有版本,每次修改都会推进版本,已提交的都会有一个序列号。
- 权限控制,每个都有相关策略,用来管理是否可修改
- 可继承,组包含子组,每一级都有相关策略,低级子组的继承高级的组,低级子组的可以重写策略。
到此为止,剩下的回头再看。
知道了,和总的模板对比之后,就是config/configtx.yaml(里面的profile部分很多,找几个例子参考参考就知道了)。发现了很多层次问题,教程也是坑啊。
configtxgen试着执行,一般报错还是挺清晰的,参照着看就可以。
这里又出现一个问题,说是没有policy指定,说的是orderer,
当然,模板里肯定有的,拷两个过来吧。模板里说:Policies defines the set of policies at this level of the config tree For Orderer policies, their canonical path is /Channel/Orderer/ 去找找呗。找不到,还是从模板里复制吧。
来补充一小段yaml知识。不然yaml都看不懂:
&用来建立锚点(defaults),<<表示合并到当前数据,*用来引用锚点。
给orderer部分和通道都加了policy。
报了新错
这是没找到msp啊。yaml放错地方了。
又报,
说是org0没有policy,模板里的organization还真有policy
醉了,从模板复制就没问题。看来还是yaml的一些格式不对。
现在遇到了权限问题,无法写入。还是root下来吧
使用root权限,成功得到了创世区块(注意fabric-cfg-path的环境变量)
又经历了application没有policy,那应该是现在都需要有policy才行。生成了channel.tx
进行到这里,教程提到两个查看证书的命令:
- fabric-ca-client certificate 用来获取admincerts信息
- fabric-ca-client getcainfo 用来获取cacerts 和 tlscacerts的信息
教程上说,我们现在的例子其实只验证了服务器的TLS,如果要双向TLS,则客户端也得发一个TLS证书去服务器来让服务器验证。
来一个最终我运行的configtx.yaml吧
Organizations:
- &org0
Name: org0
ID: org0MSP
MSPDir: ./org0/msp # 这个明显还是映射路径
# Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
Policies: &OrgPolicies
Readers:
Type: Signature
Rule: "OR('SampleOrg.member')"
# If your MSP is configured with the new NodeOUs, you might
# want to use a more specific rule like the following:
# Rule: "OR('SampleOrg.admin', 'SampleOrg.peer', 'SampleOrg.client')"
Writers:
Type: Signature
Rule: "OR('SampleOrg.member')"
# If your MSP is configured with the new NodeOUs, you might
# want to use a more specific rule like the following:
# Rule: "OR('SampleOrg.admin', 'SampleOrg.client')"
Admins:
Type: Signature
Rule: "OR('SampleOrg.admin')"
Endorsement:
Type: Signature
Rule: "OR('SampleOrg.member')"
- &org1
Name: org1
ID: org1MSP
MSPDir: ./org1/msp
AnchorPeers:
- Host: peer1-org1
Port: 7051
- &org2
Name: org2
ID: org2MSP
MSPDir: ./org2/msp
AnchorPeers:
- Host: peer1-org2
Port: 7051
################################################################################
#
# CAPABILITIES
#
################################################################################
Capabilities:
# Channel capabilities apply to both the orderers and the peers and must be
# supported by both.
# Set the value of the capability to true to require it.
Channel: &ChannelCapabilities
# V2.0 for Channel is a catchall flag for behavior which has been
# determined to be desired for all orderers and peers running at the v2.0.0
# level, but which would be incompatible with orderers and peers from
# prior releases.
# Prior to enabling V2.0 channel capabilities, ensure that all
# orderers and peers on a channel are at v2.0.0 or later.
V2_0: true
# Orderer capabilities apply only to the orderers, and may be safely
# used with prior release peers.
# Set the value of the capability to true to require it.
Orderer: &OrdererCapabilities
# V1.1 for Orderer is a catchall flag for behavior which has been
# determined to be desired for all orderers running at the v1.1.x
# level, but which would be incompatible with orderers from prior releases.
# Prior to enabling V2.0 orderer capabilities, ensure that all
# orderers on a channel are at v2.0.0 or later.
V2_0: true
# Application capabilities apply only to the peer network, and may be safely
# used with prior release orderers.
# Set the value of the capability to true to require it.
Application: &ApplicationCapabilities
# V2.0 for Application enables the new non-backwards compatible
# features and fixes of fabric v2.0.
# Prior to enabling V2.0 orderer capabilities, ensure that all
# orderers on a channel are at v2.0.0 or later.
V2_0: true
################################################################################
#
# SECTION: Application
#
# This section defines the values to encode into a config transaction or
# genesis block for application related parameters
#
################################################################################
Application: &ApplicationDefaults
# Organizations is the list of orgs which are defined as participants on
# the application side of the network
Organizations:
Policies: &ApplicationDefaultPolicies
LifecycleEndorsement:
Type: ImplicitMeta
Rule: "MAJORITY Endorsement"
Endorsement:
Type: ImplicitMeta
Rule: "MAJORITY Endorsement"
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
Capabilities:
<<: *ApplicationCapabilities
################################################################################
#
# CHANNEL
#
# This section defines the values to encode into a config transaction or
# genesis block for channel related parameters.
#
################################################################################
Channel: &ChannelDefaults
# Policies defines the set of policies at this level of the config tree
# For Channel policies, their canonical path is
# /Channel/<PolicyName>
Policies:
# Who may invoke the 'Deliver' API
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
# Who may invoke the 'Broadcast' API
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
# By default, who may modify elements at this config level
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
# Capabilities describes the channel level capabilities, see the
# dedicated Capabilities section elsewhere in this file for a full
# description
# 定义了通道级能力
Capabilities:
V2_0: true # 模板中用的引用,我这里直接拷过来,这个好像是要求所有节点高于2.0版本
################################################################################
#
# Profile
#
# - Different configuration profiles may be encoded here to be specified
# as parameters to the configtxgen tool
# 可能会有不同的profile配置,作为参数传递给configtxgen工具
#
################################################################################
Profiles:
# 创世区块的命令参数,有点类似模板中SampleSingleMSPSolo
OrgsOrdererGenesis:
<<: *ChannelDefaults # 应该是有版本更新什么的,这里需要通道设置
Orderer: # 这里也可以用引用,模板里就是,但这个例子是直接在这写了
# Orderer Type: orderer节点启动的类型 "solo" or "kafka"
OrdererType: solo
Addresses:
- orderer1-org0:7050
# Batch Timeout: The amount of time to wait before creating a batch
BatchTimeout: 2s
# Batch Size: Controls the number of messages batched into a block
BatchSize:
# Max Message Count: The maximum number of messages to permit in a batch
MaxMessageCount: 10
# Absolute Max Bytes: The absolute maximum number of bytes allowed for
# the serialized messages in a batch.
AbsoluteMaxBytes: 99 MB
# Preferred Max Bytes: The preferred maximum number of bytes allowed for
# the serialized messages in a batch. A message larger than the preferred
# max bytes will result in a batch larger than preferred max bytes.
PreferredMaxBytes: 512 KB
# Kafka:
# # Brokers: A list of Kafka brokers to which the orderer connects
# # NOTE: Use IP:port notation
# Brokers:
# - 127.0.0.1:9092
# Organizations 这里定义的是组织列表,这些组织在网络里作为参与者在orderer一边
Organizations:
- *org0
# 还是加上策略
Policies:
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
# BlockValidation specifies what signatures must be included in the block
# from the orderer for the peer to validate it.
BlockValidation:
Type: ImplicitMeta
Rule: "ANY Writers"
Capabilities:
V2_0: true
# 联盟,那就是剩下的组织了呗
Consortiums:
SampleConsortium:
Organizations:
- <<: *org1
Policies:
<<: *OrgPolicies
Admins:
Type: Signature
Rule: "OR('SampleOrg.member')"
- <<: *org2
Policies:
<<: *OrgPolicies
Admins:
Type: Signature
Rule: "OR('SampleOrg.member')"
# 另一个命令的参数
OrgsChannel:
<<: *ChannelDefaults
Consortium: SampleConsortium
Application:
<<: *ApplicationDefaults
Organizations:
- <<: *org1
Policies:
<<: *OrgPolicies
Admins:
Type: Signature
Rule: "OR('SampleOrg.member')"
- <<: *org2
Policies:
<<: *OrgPolicies
Admins:
Type: Signature
Rule: "OR('SampleOrg.member')"
启动Orderer
还是注意修改位置,
我把上面生成的两个放在了hyperledger/org0/orderer下面,结构这样子。
然后映射的路径也是那里。
整体的docker-compose.yml现在这样子
version: "3"
networks:
fabric-ca: # 这是一个自定义网络名称
services:
# 用于3个组织的TLS CA 7052
ca-tls:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: ca-tls
networks: # 然后在这里指定才行
- fabric-ca
ports:
- 7052:7052 # 映射端口7052,容器
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=ca-tls
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/tls-ca:/tmp/hyperledger/fabric-ca
command: sh -c 'fabric-ca-server start -d -b tls-ca-admin:tls-ca-adminpw --port 7052' # 命令还是启动一个CA服务器,端口换成7052
# 排序节点CA 7053
rca-org0:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org0
networks:
- fabric-ca
ports:
- 7053:7053
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org0
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org0/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org0/orderer:/tmp/hyperledger/orderer # 还是映射
- ./hyperledger/org0/admin:/tmp/hyperledger/admin #
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org0-admin:rca-org0-adminpw --port 7053' #这里的用户名其实我觉得真的部署也可以直接写admin
# 组织1的CA 7054
rca-org1:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org1
networks:
- fabric-ca
ports:
- 7054:7054
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org1
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org1/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org1/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
- ./hyperledger/org1/peer2:/tmp/hyperledger/peer2
- ./hyperledger/org1/admin:/tmp/hyperledger/admin
# - ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1 # 临时用一下,然后再去掉
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org1-admin:rca-org1-adminpw' # 这里没有指定端口7054,因为默认就是7054
# 组织2的CA 7055
rca-org2:
image: hyperledger/fabric-ca:amd64-1.4.9
container_name: rca-org2
networks:
- fabric-ca
ports:
- 7055:7055
environment:
- FABRIC_CA_SERVER_HOME=/tmp/hyperledger/fabric-ca/crypto
- FABRIC_CA_SERVER_TLS_ENABLED=true
- FABRIC_CA_SERVER_CSR_CN=rca-org2
- FABRIC_CA_SERVER_CSR_HOSTS=0.0.0.0
- FABRIC_CA_SERVER_DEBUG=true
volumes:
- ./hyperledger/org2/ca:/tmp/hyperledger/fabric-ca
- ./hyperledger/org2/peer1:/tmp/hyperledger/peer1 #这都是用来临时生成一个节点的加密材料的
- ./hyperledger/org2/peer2:/tmp/hyperledger/peer2
- ./hyperledger/org2/admin:/tmp/hyperledger/admin
command: /bin/bash -c 'fabric-ca-server start -d -b rca-org2-admin:rca-org2-adminpw --port 7055'
# org1的peer1节点 7051
peer1-org1:
image: hyperledger/fabric-peer:2.2
container_name: peer1-org1
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer1-org1
- CORE_PEER_ADDRESS=peer1-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer1/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org1/peer1/tls-msp/keystore/key.pem # 这个key.pem是我们刚刚改过的名字
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1-org1:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1/peer1
volumes:
- /var/run:/host/var/run
- ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1
# org1的peer2节点 7051
peer2-org1:
image: hyperledger/fabric-peer:2.2
container_name: peer2-org1
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer2-org1
- CORE_PEER_ADDRESS=peer2-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer2/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=grpc=debug:info
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org1/peer2/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org1/peer2/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer2/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer2-org1:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
- CORE_PEER_GOSSIP_BOOTSTRAP=peer1-org1:7051
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1/peer2
volumes:
- /var/run:/host/var/run
- ./hyperledger/org1/peer2:/tmp/hyperledger/org1/peer2
# org2的peer1节点 7051
peer1-org2:
image: hyperledger/fabric-peer:2.2
container_name: peer1-org2
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer1-org2
- CORE_PEER_ADDRESS=peer1-org2:7051
- CORE_PEER_LOCALMSPID=org2MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org2/peer1/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org2/peer1/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org2/peer1/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org2/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1-org2:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org2/peer1
volumes:
- /var/run:/host/var/run
- ./hyperledger/org2/peer1:/tmp/hyperledger/org2/peer1
# org2的peer2节点 7051
peer2-org2:
image: hyperledger/fabric-peer:2.2
container_name: peer2-org2
networks:
- fabric-ca
environment:
- CORE_PEER_ID=peer2-org2
- CORE_PEER_ADDRESS=peer2-org2:7051
- CORE_PEER_LOCALMSPID=org2MSP
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org2/peer2/msp
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=testop_fabric-ca
- FABRIC_LOGGING_SPEC=debug
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/tmp/hyperledger/org2/peer2/tls-msp/signcerts/cert.pem
- CORE_PEER_TLS_KEY_FILE=/tmp/hyperledger/org2/peer2/tls-msp/keystore/key.pem
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org2/peer2/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer2-org2:7051
- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
- CORE_PEER_GOSSIP_BOOTSTRAP=peer1-org2:7051
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org2/peer2
volumes:
- /var/run:/host/var/run
- ./hyperledger/org2/peer2:/tmp/hyperledger/org2/peer2
# 排序节点
orderer1-org0:
image: hyperledger/fabric-orderer:2.2
container_name: orderer1-org0
networks:
- fabric-ca
environment:
- ORDERER_HOME=/tmp/hyperledger/orderer
- ORDERER_HOST=orderer1-org0
- ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
- ORDERER_GENERAL_GENESISMETHOD=file
- ORDERER_GENERAL_GENESISFILE=/tmp/hyperledger/org0/orderer/genesis.block
- ORDERER_GENERAL_LOCALMSPID=org0MSP
- ORDERER_GENERAL_LOCALMSPDIR=/tmp/hyperledger/org0/orderer/msp
- ORDERER_GENERAL_TLS_ENABLED=true
- ORDERER_GENERAL_TLS_CERTIFICATE=/tmp/hyperledger/org0/orderer/tls-msp/signcerts/cert.pem
- ORDERER_GENERAL_TLS_PRIVATEKEY=/tmp/hyperledger/org0/orderer/tls-msp/keystore/key.pem
- ORDERER_GENERAL_TLS_ROOTCAS=[/tmp/hyperledger/org0/orderer/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem]
- ORDERER_GENERAL_LOGLEVEL=debug
- ORDERER_DEBUG_BROADCASTTRACEDIR=data/logs
volumes:
- ./hyperledger/org0/orderer:/tmp/hyperledger/org0/orderer/
9个容器,都启动了
创建命令行容器
接下来是用于操作的命令行容器。
与peer通信需要CLI容器,该容器包含相应的命令文件,允许您发出与peer相关的命令。您将为每个组织创建一个CLI容器。
在本例中,我们在每个组织的Peer1所在的主机上启动一个CLI容器。
这俩容器加进去就行,注意映射路径(根本目的就是要找到启动需要的相关msp和弄好其他设置)
# org1的命令行节点
cli-org1:
image: hyperledger/fabric-tools:2.2
container_name: cli-org1
networks:
- fabric-ca
tty: true
stdin_open: true
environment:
- GOPATH=/opt/gopath
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- FABRIC_LOGGING_SPEC=DEBUG
- CORE_PEER_ID=cli-org1
- CORE_PEER_ADDRESS=peer1-org1:7051
- CORE_PEER_LOCALMSPID=org1MSP
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org1/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org1/peer1/msp
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org1
command: sh
volumes:
- ./hyperledger/org1/peer1:/tmp/hyperledger/org1/peer1
- ./hyperledger/org1/peer1/assets/chaincode:/opt/gopath/src/github.com/hyperledger/fabric-samples/chaincode
- ./hyperledger/org1/admin:/tmp/hyperledger/org1/admin
# org2的命令行节点
cli-org2:
image: hyperledger/fabric-tools:2.2
container_name: cli-org2
networks:
- fabric-ca
tty: true
stdin_open: true
environment:
- GOPATH=/opt/gopath
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- FABRIC_LOGGING_SPEC=DEBUG
- CORE_PEER_ID=cli-org2
- CORE_PEER_ADDRESS=peer1-org2:7051
- CORE_PEER_LOCALMSPID=org2MSP
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_TLS_ROOTCERT_FILE=/tmp/hyperledger/org2/peer1/tls-msp/tlscacerts/tls-0-0-0-0-7052.pem
- CORE_PEER_MSPCONFIGPATH=/tmp/hyperledger/org2/peer1/msp
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/org2
volumes:
- ./hyperledger/org2/peer1:/tmp/hyperledger/org2/peer1
- ./hyperledger/org1/peer1/assets/chaincode:/opt/gopath/src/github.com/hyperledger/fabric-samples/chaincode
- ./hyperledger/org2/admin:/tmp/hyperledger/org2/admin
command: sh
现在有11个容器了。
下面开始在这两个容器上进行命令行操作。
创建并加入通道
在peer1里运行也不行,看来就是注册登记时候出问题,没有把admin-org1登记为admin类型
那这样牵一发动全身,org1的admin重新来,要求peer1节点重新来。
把channel.tx拷贝到peer1上,这样才能执行下面的命令。
添加了一个从fabric-sample/test-network/organizations/peerOrganizations,随便找了一个msp,都有一个文件config.yaml。我拷贝过来放到admin的msp下,里面是NodeOU的配置,注意改改文件名,然后就报了其他错了
NodeOUs:
Enable: true
ClientOUIdentifier:
Certificate: cacerts/0-0-0-0-7054.pem
OrganizationalUnitIdentifier: client
PeerOUIdentifier:
Certificate: cacerts/0-0-0-0-7054.pem
OrganizationalUnitIdentifier: peer
AdminOUIdentifier:
Certificate: cacerts/0-0-0-0-7054.pem
OrganizationalUnitIdentifier: admin
OrdererOUIdentifier:
Certificate: cacerts/0-0-0-0-7054.pem
OrganizationalUnitIdentifier: orderer
注意:我感觉应该可以续上fabric的教程了。而且,不知道是不是版本问题,似乎有些配置文件不存在,还是对照一下fabric-sample比较好。
可能用fabric-sample里的配置文件会好一些。
也可能根本没必要这样从头,直接改一点点,聚焦到核心工作上就好。
经过测试,发现确实是可以创建通道的。那么就可以确定,就是前面这个过程生成的材料其实不符合要求。一定是某些配置出现了问题。。等后面来尝试解决
创建通道
https://hyperledger-fabric.readthedocs.io/en/release-2.2/create_channel/create_channel.html
参考
- https://www.cnblogs.com/jxd283465/p/13086816.html
- https://blog.csdn.net/shebao3333/article/details/103558715