From be73c5439e6cbfab08ec0d0b74e3266e2eb0dc69 Mon Sep 17 00:00:00 2001 From: liuchunlong <631521383@qq.com> Date: Wed, 11 Jul 2018 14:38:00 +0800 Subject: [PATCH] 1.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 脚本自动分发到各个服务器 * orderer增加kafka集群 * 账本存储使用couchdb --- README.md | 81 +++++----- fabric-ca/fabric.config | 94 +++++++++--- fabric-ca/makeDocker.sh | 174 +++++++++++++++++++++- fabric-ca/network_builder.sh | 236 +++++++++++++++++++++++++++--- fabric-ca/peer-bootstrap.sh | 4 +- fabric-ca/scripts/env.sh | 52 ++++++- fabric-ca/scripts/file_delete.sh | 76 ++++++++++ fabric-ca/scripts/file_exits.sh | 3 +- fabric-ca/scripts/file_scp.sh | 19 ++- fabric-ca/scripts/setup-fabric.sh | 16 +- fabric-ca/setup-bootstrap.sh | 2 +- fabric-ca/zk-kafka-bootstrap.sh | 71 +++++++++ ica-tree.png | Bin 15160 -> 0 bytes 13 files changed, 730 insertions(+), 98 deletions(-) create mode 100644 fabric-ca/scripts/file_delete.sh create mode 100644 fabric-ca/zk-kafka-bootstrap.sh delete mode 100644 ica-tree.png diff --git a/README.md b/README.md index dca7c8a..a43fbe6 100644 --- a/README.md +++ b/README.md @@ -16,40 +16,24 @@ ```bash root@vm10-249-0-4:~/fabric-web/fabric-ca# chmod +x *.sh root@vm10-249-0-4:~/fabric-web/fabric-ca# -root@vm10-249-0-4:~/fabric-web/fabric-ca# root@vm10-249-0-4:~/fabric-web/fabric-ca# chmod +x scripts/*.sh ``` -### 0. 网络拓扑 +### Z、网络拓扑 + +通过`fabric.config`定义网络拓扑结构。 -通过`fabric.config`定义网络拓扑结构 +> 💡 确认`setup`节点的IP与第一个Peer组织的第一个peer节点一致(`setup`脚本是基于第一个节点身份的), +> 否则在执行实例化链码时报错:Timeout *** -### 1.构建项目,为不同节点打包脚本 +### 一、构建项目,为不同节点打包脚本 ```bash ./network_builder.sh ``` -再正式开始前,确保你已经正确完成下列步骤执行: +**在正式开始前,确保你已经正确完成下列步骤执行**: -为了方便起见,我们拿代码中提供的示例`fabric.config`配置文件做说明: - -* 将`build`目录下生成的文件夹分别拷贝到相应节点的 **_`fabric.config`配置中指定用户的指定目录_** 下; - - ```bash - scp -r ica ubuntu@:~/fabric/ica - scp -r rca ubuntu@:~/fabric/rca - scp -r peer ubuntu@:~/fabric/peer - scp -r orderer ubuntu@:~/fabric/orderer - scp -r setup ubuntu@:~/fabric/setup - ``` - - > 务必将`setup`拷贝到第一个Peer组织的第一个peer节点上执行,否则在执行实例化链码时报错:Timeout... - - 所有节点的目录应该类似这个样子: - - ![](./ica-tree.png) - * 每个节点都已下载所需的fabric镜像; 可执行如下命令下载镜像: @@ -58,7 +42,7 @@ root@vm10-249-0-4:~/fabric-web/fabric-ca# chmod +x scripts/*.sh ./down-images.sh ``` -### 2.启动CA服务 +### 二、启动CA服务 对于每一个组织都要启动一个rca和ica服务。 @@ -118,7 +102,7 @@ root@vm10-249-0-4:~/fabric-web/fabric-ca# chmod +x scripts/*.sh 这些工作脚本也已经帮我们完成了!~ ✌ -### 3. 启动setup +### 三、启动setup setup容器用于: @@ -155,15 +139,41 @@ setup-bootstrap.sh [-h] [-?] [-d] ~~脚本会将编译生成的`fabric-ca-server`和`fabric-ca-client`保存在`$GOPATH/bin`目录下。~~ * 此外,你还需要配置当前机器的`/etc/host`,内容参见`build/host.config`。 -* 将安装的**_链码_**复制到'setup'同级目录下。 如果你执行完上述,那么来启动`setup`吧!~😍 ```bash -./setup-bootstrap.sh +sudo ./setup-bootstrap.sh ``` -### 4. 启动orderer +### 四、启动Zookeeper 与 Kafka集群 + +```text +zk-kafka-bootstrap.sh <-z|-k> [-?] + -h|-? 获取此帮助信息 + -z 启动zookeeper节点 + -k 启动kafka节点 +``` + +假设zookeeper与kafka个配置3台。那么启动脚本如下: + +**启动Zookeeper**: + +```bash +./zk-kafka-bootstrap.sh -z 1 +./zk-kafka-bootstrap.sh -z 2 +./zk-kafka-bootstrap.sh -z 3 +``` + +**启动Kafka**: + +```bash +./zk-kafka-bootstrap.sh -k 1 +./zk-kafka-bootstrap.sh -k 2 +./zk-kafka-bootstrap.sh -k 3 +``` + +### 五、启动orderer ```text orderer-bootstrap.sh [-h] [-?] @@ -176,7 +186,7 @@ orderer-bootstrap.sh [-h] [-?] ./orderer-bootstrap.sh ``` -### 5. 启动peer +### 六、启动peer ```text peer-bootstrap.sh [-h] [-?] @@ -189,13 +199,14 @@ peer-bootstrap.sh [-h] [-?] ./peer-bootstrap.sh ``` -## TODO +## 版本历史 -- orderer增加kafka集群 -- 账本存储使用couchdb +### v1.1.1 -## 版本历史 +* 新增`expect`,免去手动输入密码的烦恼; -### v1.0.1 +### v1.1.2 -* 新增`expect`,免去手动输入密码的烦恼; \ No newline at end of file +* 脚本自动分发到各个服务器 +* orderer增加kafka集群 +* 账本存储使用couchdb \ No newline at end of file diff --git a/fabric-ca/fabric.config b/fabric-ca/fabric.config index 3753508..9701c6e 100644 --- a/fabric-ca/fabric.config +++ b/fabric-ca/fabric.config @@ -3,90 +3,136 @@ "PEER_ORGS": "org1 org2", "NUM_PEERS": 2, "NUM_ORDERERS": 1, + "NUM_ZOOKEEPER": 3, + "NUM_KAFKA": 3, "NET_CONFIG": { + "ZOOKEEPER_CLUSTER": [ + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.84", + "PATH": "~/fabric/zk", + "PWD": "***" + }, + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.85", + "PATH": "~/fabric/zk", + "PWD": "***" + }, + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.86", + "PATH": "~/fabric/zk", + "PWD": "***" + } + ], + "KAFKA_CLUSTER": [ + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.87", + "PATH": "~/fabric/zk", + "PWD": "***" + }, + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.88", + "PATH": "~/fabric/zk", + "PWD": "***" + }, + { + "USER_NAME": "ubuntu", + "IP": "127.0.0.89", + "PATH": "~/fabric/zk", + "PWD": "***" + } + ], "org0": { "RCA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.90", + "IP": "127.0.0.90", "PATH": "~/fabric/rca", - "PWD": "qwe123456" + "PWD": "***" }, "ICA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.91", + "IP": "127.0.0.91", "PATH": "~/fabric/ica", - "PWD": "qwe123456" + "PWD": "***" }, "ORDERERS": [ { "USER_NAME": "ubuntu", - "IP": "120.131.13.92", + "IP": "127.0.0.92", "PATH": "~/fabric/orderer", - "PWD": "qwe123456" + "PWD": "***" } ] }, "org1": { "RCA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.93", + "IP": "127.0.0.93", "PATH": "~/fabric/rca", - "PWD": "qwe123456" + "PWD": "***" }, "ICA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.94", + "IP": "127.0.0.94", "PATH": "~/fabric/ica", - "PWD": "qwe123456" + "PWD": "***" }, "PEERS": [ { "USER_NAME": "ubuntu", - "IP": "120.131.13.95", + "IP": "127.0.0.95", "PATH": "~/fabric/peer", - "PWD": "qwe123456" + "PWD": "***", + "COUCHDB_IP": "127.0.0.1" }, { "USER_NAME": "ubuntu", - "IP": "120.131.13.96", + "IP": "127.0.0.96", "PATH": "~/fabric/peer", - "PWD": "qwe123456" + "PWD": "***", + "COUCHDB_IP": "127.0.0.1" } ] }, "org2": { "RCA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.97", + "IP": "127.0.0.97", "PATH": "~/fabric/rca", - "PWD": "qwe123456" + "PWD": "***" }, "ICA": { "USER_NAME": "ubuntu", - "IP": "120.131.13.98", + "IP": "127.0.0.98", "PATH": "~/fabric/ica", - "PWD": "qwe123456" + "PWD": "***" }, "PEERS": [ { "USER_NAME": "ubuntu", - "IP": "120.131.13.99", + "IP": "127.0.0.99", "PATH": "~/fabric/peer", - "PWD": "qwe123456" + "PWD": "***", + "COUCHDB_IP": "127.0.0.1" }, { "USER_NAME": "ubuntu", - "IP": "120.131.13.100", + "IP": "127.0.0.100", "PATH": "~/fabric/peer", - "PWD": "qwe123456" + "PWD": "***", + "COUCHDB_IP": "127.0.0.1" } ] }, "SETUP": { "USER_NAME": "ubuntu", - "IP": "120.131.13.101", + "IP": "127.0.0.95", "PATH": "~/fabric/setup", - "PWD": "qwe123456" + "PWD": "***" } } } \ No newline at end of file diff --git a/fabric-ca/makeDocker.sh b/fabric-ca/makeDocker.sh index 38d7f3f..72df4ff 100644 --- a/fabric-ca/makeDocker.sh +++ b/fabric-ca/makeDocker.sh @@ -109,6 +109,7 @@ function writeSetupFabric { image: hyperledger/fabric-ca-tools command: /bin/bash -c '/scripts/setup-fabric.sh 2>&1 | tee /$SETUP_LOGFILE; sleep 99999' volumes: + - ./fabric.config:/fabric.config - ./scripts:/scripts - ./$DATA:/$DATA depends_on:" @@ -135,6 +136,20 @@ function writeRootFabricCA { # 为每一个orderer和peer容器编写服务 function writeStartFabric { + COUNT=1 + while [[ "$COUNT" -le $NUM_ZOOKEEPER ]]; do + initZKVars $COUNT + writeZK $COUNT $NUM_ZOOKEEPER + COUNT=$((COUNT+1)) + done + + COUNT=1 + while [[ "$COUNT" -le $NUM_KAFKA ]]; do + initKafkaVars $COUNT + writeKafka $COUNT $NUM_KAFKA $NUM_ZOOKEEPER + COUNT=$((COUNT+1)) + done + for ORG in $ORDERER_ORGS; do COUNT=1 while [[ "$COUNT" -le $NUM_ORDERERS ]]; do @@ -148,12 +163,98 @@ function writeStartFabric { COUNT=1 while [[ "$COUNT" -le $NUM_PEERS ]]; do initPeerVars $ORG $COUNT + writeCouchdb writePeer COUNT=$((COUNT+1)) done done } +# Zookeeper +function writeZK { + + ZK_ID=$1 + ZK_NUM=$2 + + echo -n " $ZK_NAME: + container_name: $ZK_NAME + image: hyperledger/fabric-zookeeper + restart: always + ports: + - 2181:2181 + - 2888:2888 + - 3888:3888 + environment: + - ZOO_MY_ID=$ZK_ID + - ZOO_SERVERS=" + local COUNT=1 + while [[ "$COUNT" -le $ZK_NUM ]]; do + initZKVars $COUNT + if [[ "$COUNT" -eq $ZK_NUM ]]; then + if [[ "$COUNT" -eq $ZK_ID ]]; then + echo "server."$COUNT"=0.0.0.0:2888:3888" + else + echo "server."$COUNT"=$ZK_HOST:2888:3888" + fi + else + if [[ "$COUNT" -eq $ZK_ID ]]; then + echo -n "server."$COUNT"=0.0.0.0:2888:3888 " + else + echo -n "server."$COUNT"=$ZK_HOST:2888:3888 " + fi + fi + COUNT=$((COUNT+1)) + done + echo " extra_hosts:" + genZKHosts + echo "" +} + +# Kafka +function writeKafka { + + KAFKA_ID=$1 + KAFKA_NUM=$2 + ZK_NUM=$2 + + echo -n " $KAFKA_NAME: + container_name: $KAFKA_NAME + image: hyperledger/fabric-kafka + restart: always + hostname: $KAFKA_HOST + ports: + - 9092:9092 + environment: + - KAFKA_MESSAGE_MAX_BYTES=103809024 # 99 * 1024 * 1024 B + - KAFKA_REPLICA_FETCH_MAX_BYTES=103809024 # 99 * 1024 * 1024 B + - KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false + - KAFKA_BROKER_ID=$KAFKA_ID + - KAFKA_MIN_INSYNC_REPLICAS=2 + - KAFKA_DEFAULT_REPLICATION_FACTOR=3 + - KAFKA_ZOOKEEPER_CONNECT=" + local COUNT=1 + while [[ "$COUNT" -le $ZK_NUM ]]; do + initZKVars $COUNT + if [[ "$COUNT" -eq $ZK_NUM ]]; then + echo "$ZK_HOST:2181" + else + echo -n "$ZK_HOST:2181," + fi + COUNT=$((COUNT+1)) + done + echo " depends_on:" + COUNT=1 + while [[ "$COUNT" -le $ZK_NUM ]]; do + initZKVars $COUNT + echo " - $ZK_NAME" + COUNT=$((COUNT+1)) + done + echo " extra_hosts:" + genZKHosts + genKafkaHosts + echo "" +} + # Orderer容器服务 function writeOrderer { @@ -181,6 +282,10 @@ function writeOrderer { - ORDERER_GENERAL_TLS_CLIENTROOTCAS=[$CA_CHAINFILE] - ORDERER_GENERAL_LOGLEVEL=debug - ORDERER_DEBUG_BROADCASTTRACEDIR=$LOGDIR + # kafka + - ORDERER_KAFKA_RETRY_SHORTINTERVAL=1s + - ORDERER_KAFKA_RETRY_SHORTTOTAL=30s + - ORDERER_KAFKA_VERBOSE=true - ORDERER_HOME=$MYHOME - ORDERER_HOST=$ORDERER_HOST - ORDERER_LOGFILE=$ORDERER_LOGFILE @@ -192,17 +297,46 @@ function writeOrderer { volumes: - ./scripts:/scripts - ./$DATA:/$DATA - depends_on: - - setup - ports: + depends_on:" + COUNT=1 + while [[ "$COUNT" -le $ZK_NUM ]]; do + initZKVars $COUNT + echo " - $ZK_NAME" + COUNT=$((COUNT+1)) + done + COUNT=1 + while [[ "$COUNT" -le $KAFKA_NUM ]]; do + initKafkaVars $COUNT + echo " - $KAFKA_NAME" + COUNT=$((COUNT+1)) + done + echo " ports: - 7050:7050 extra_hosts:" + genKafkaHosts genCAHosts genOrdererHosts genPeerHosts echo "" } +function writeCouchdb { + + echo " $PEER_COUCHDB_NAME: + container_name: $PEER_COUCHDB_NAME + image: hyperledger/fabric-couchdb + # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password + # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. + environment: + - COUCHDB_USER= + - COUCHDB_PASSWORD= + # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, + # for example map it to utilize Fauxton User Interface in dev environments. + ports: + - 5984:5984" + echo "" +} + # Peer容器服务 function writePeer { @@ -239,6 +373,14 @@ function writePeer { - CORE_PEER_GOSSIP_ORGLEADER=false - CORE_PEER_GOSSIP_EXTERNALENDPOINT=$PEER_HOST:7051 # 节点被组织外节点感知时的地址 - CORE_PEER_GOSSIP_SKIPHANDSHAKE=true + # couchdb + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=$PEER_COUCHDB_HOST:5984 + # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD + # provide the credentials for ledger to connect to CouchDB. The username and password must + # match the username and password set for the associated CouchDB. + - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= + - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD= - PEER_NAME=$PEER_NAME - PEER_HOME=$MYHOME - PEER_HOST=$PEER_HOST @@ -259,12 +401,13 @@ function writePeer { - ./$DATA:/$DATA - /var/run:/host/var/run depends_on: - - setup + - $PEER_COUCHDB_NAME ports: - 7051:7051 - 7052:7052 - 7053:7053 extra_hosts:" + genCouchdbHosts genCAHosts genOrdererHosts genPeerHosts @@ -326,6 +469,24 @@ function genCAHosts { done } +function genZKHosts { + COUNT=1 + while [[ "$COUNT" -le $NUM_ZOOKEEPER ]]; do + initZKVars $COUNT + echo " - \"${ZK_HOST}:"$(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].IP')"\"" + COUNT=$((COUNT+1)) + done +} + +function genKafkaHosts { + COUNT=1 + while [[ "$COUNT" -le $NUM_KAFKA ]]; do + initKafkaVars $COUNT + echo " - \"${KAFKA_HOST}:"$(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].IP')"\"" + COUNT=$((COUNT+1)) + done +} + function genOrdererHosts { local ORG for ORG in $ORDERER_ORGS; do @@ -338,6 +499,11 @@ function genOrdererHosts { done } +function genCouchdbHosts { + + echo " - \"${PEER_COUCHDB_HOST}:"$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.PEERS['$((COUNT-1))'].COUCHDB_IP')"\"" +} + function genPeerHosts { local ORG for ORG in $PEER_ORGS; do diff --git a/fabric-ca/network_builder.sh b/fabric-ca/network_builder.sh index 6ebb559..e1db67e 100644 --- a/fabric-ca/network_builder.sh +++ b/fabric-ca/network_builder.sh @@ -12,16 +12,177 @@ SDIR=$(dirname "$0") source ${SDIR}/scripts/env.sh cd ${SDIR} +function distriRCA { + + local rca_path=./build/rca + for ORG in $ORGS; do + rca_user=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.RCA.USER_NAME') + rca_ip=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.RCA.IP') + rca_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.RCA.PWD') + rca_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.RCA.PATH') + + echo "Delete remote ${rca_ip} file ${rca_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${rca_user} ${rca_ip} ${rca_pwd} ${rca_remote_path} + echo "Copy file ${rca_path} to remote ${rca_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${rca_user} ${rca_ip} ${rca_pwd} ${rca_remote_path} ${rca_path} "to" + done +} + +function distriICA { + + local ica_path=./build/ica + for ORG in $ORGS; do + ica_user=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ICA.USER_NAME') + ica_ip=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ICA.IP') + ica_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ICA.PWD') + ica_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ICA.PATH') + + echo "Delete remote ${ica_ip} file ${ica_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${ica_user} ${ica_ip} ${ica_pwd} ${ica_remote_path} + echo "Copy file ${ica_path} to remote ${ica_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${ica_user} ${ica_ip} ${ica_pwd} ${ica_remote_path} ${ica_path} "to" + done +} + +function distriSetup { + + local setup_path=./build/setup + local chaincode_path=../chaincode + + local setup_user=$(cat fabric.config | jq -r '.NET_CONFIG.SETUP.USER_NAME') + local setup_ip=$(cat fabric.config | jq -r '.NET_CONFIG.SETUP.IP') + local setup_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.SETUP.PWD') + local setup_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.SETUP.PATH') + + echo "Delete remote ${setup_ip} file ${setup_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${setup_user} ${setup_ip} ${setup_pwd} ${setup_remote_path} + echo "Copy file ${setup_path} to remote ${setup_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${setup_user} ${setup_ip} ${setup_pwd} ${setup_remote_path} ${setup_path} "to" + + # 链码 + local chaincode_remote_path=$setup_remote_path"/../chaincode" + echo "Delete remote ${setup_ip} file ${chaincode_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${setup_user} ${setup_ip} ${setup_pwd} ${chaincode_remote_path} + echo "Copy file ${chaincode_path} to remote ${setup_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${setup_user} ${setup_ip} ${setup_pwd} ${chaincode_remote_path} ${chaincode_path} "to" +} + +function distriZK { + + local zk_path=./build/zk + + local COUNT=1 + while [[ "$COUNT" -le $NUM_ZOOKEEPER ]]; do + + zk_user=$(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].USER_NAME') + zk_ip=$(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].IP') + zk_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].PWD') + zk_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].PATH') + + echo "Delete remote ${zk_ip} file ${zk_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${zk_user} ${zk_ip} ${zk_pwd} ${zk_remote_path} + echo "Copy file ${zk_path} to remote ${zk_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${zk_user} ${zk_ip} ${zk_pwd} ${zk_remote_path} ${zk_path} "to" + + COUNT=$((COUNT+1)) + done + + COUNT=1 + while [[ "$COUNT" -le $NUM_KAFKA ]]; do + zk_user=$(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].USER_NAME') + zk_ip=$(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].IP') + zk_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].PWD') + zk_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].PATH') + + echo "Delete remote ${zk_ip} file ${zk_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${zk_user} ${zk_ip} ${zk_pwd} ${zk_remote_path} + echo "Copy file ${zk_path} to remote ${zk_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${zk_user} ${zk_ip} ${zk_pwd} ${zk_remote_path} ${zk_path} "to" + + COUNT=$((COUNT+1)) + done +} + +function distriOrderer { + + local orderer_path=./build/orderer + + for ORG in $ORDERER_ORGS; do + COUNT=1 + while [[ "$COUNT" -le $NUM_ORDERERS ]]; do + orderer_user=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ORDERERS['$((COUNT-1))'].USER_NAME') + orderer_ip=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ORDERERS['$((COUNT-1))'].IP') + orderer_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ORDERERS['$((COUNT-1))'].PWD') + orderer_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.ORDERERS['$((COUNT-1))'].PATH') + + echo "Delete remote ${orderer_ip} file ${orderer_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${orderer_user} ${orderer_ip} ${orderer_pwd} ${orderer_remote_path} + echo "Copy file ${orderer_path} to remote ${orderer_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${orderer_user} ${orderer_ip} ${orderer_pwd} ${orderer_remote_path} ${orderer_path} "to" + + COUNT=$((COUNT+1)) + done + done +} + +function distriPeer { + + local peer_path=./build/peer + + for ORG in $PEER_ORGS; do + COUNT=1 + while [[ "$COUNT" -le $NUM_PEERS ]]; do + peer_user=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.PEERS['$((COUNT-1))'].USER_NAME') + peer_ip=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.PEERS['$((COUNT-1))'].IP') + peer_pwd=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.PEERS['$((COUNT-1))'].PWD') + peer_remote_path=$(cat fabric.config | jq -r '.NET_CONFIG.'"${ORG}"'.PEERS['$((COUNT-1))'].PATH') + + echo "Delete remote ${peer_ip} file ${peer_remote_path}" + # 删除远程服务器文件 + ${SDIR}/scripts/file_delete.sh ${peer_user} ${peer_ip} ${peer_pwd} ${peer_remote_path} + echo "Copy file ${peer_path} to remote ${peer_ip}" + # 拷贝文件到远程服务器 + ${SDIR}/scripts/file_scp.sh ${peer_user} ${peer_ip} ${peer_pwd} ${peer_remote_path} ${peer_path} "to" + + COUNT=$((COUNT+1)) + done + done +} + function package { + # jq --raw-output / -r + # With this option, if the filter´s result is a string then it will be written directly to standard output rather than being formatted as a JSON string with + # quotes. This can be useful for making jq filters talk to non-JSON-based systems. + + # zookeeper集群节点数量 + NUM_ZOOKEEPER=$(cat fabric.config | jq -r '.NUM_ZOOKEEPER') + # kafka集群节点数量 + NUM_KAFKA=$(cat fabric.config | jq -r '.NUM_KAFKA') # orderer组织的名称 - local ORDERER_ORGS=$(cat fabric.config | jq -r '.ORDERER_ORGS') + ORDERER_ORGS=$(cat fabric.config | jq -r '.ORDERER_ORGS') # peer组织的名称 - local PEER_ORGS=$(cat fabric.config | jq -r '.PEER_ORGS') + PEER_ORGS=$(cat fabric.config | jq -r '.PEER_ORGS') # 每一个peer组织的peers数量 - local NUM_PEERS=$(cat fabric.config | jq -r '.NUM_PEERS') + NUM_PEERS=$(cat fabric.config | jq -r '.NUM_PEERS') # 每一个orderer组织的orderer节点的数量 - local NUM_ORDERERS=$(cat fabric.config | jq -r '.NUM_ORDERERS') + NUM_ORDERERS=$(cat fabric.config | jq -r '.NUM_ORDERERS') + # 所有组织名称 + ORGS="$ORDERER_ORGS $PEER_ORGS" # sed on MacOSX does not support -i flag with a null extension. We will use # 't' for our back-up's extension and delete it at the end of the function @@ -32,6 +193,8 @@ function package { OPTS="-i" fi + sed $OPTS "s/NUM_ZOOKEEPER_PLACEHOLDER/${NUM_ZOOKEEPER}/g" ${SDIR}/scripts/env.sh + sed $OPTS "s/NUM_KAFKA_PLACEHOLDER/${NUM_KAFKA}/g" ${SDIR}/scripts/env.sh sed $OPTS "s/ORDERER_ORGS_PLACEHOLDER/\"${ORDERER_ORGS}\"/g" ${SDIR}/scripts/env.sh sed $OPTS "s/PEER_ORGS_PLACEHOLDER/\"${PEER_ORGS}\"/g" ${SDIR}/scripts/env.sh sed $OPTS "s/NUM_PEERS_PLACEHOLDER/${NUM_PEERS}/g" ${SDIR}/scripts/env.sh @@ -49,20 +212,26 @@ function package { cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/rca/scripts/file_exits.sh cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/rca/scripts/file_scp.sh + distriRCA + ########################## 打包ica ########################## - log "===> Package ICA files" - mkdir -p ${SDIR}/build/ica/scripts - cp ${SDIR}/ica-bootstrap.sh ${SDIR}/build/ica/ica-bootstrap.sh - cp ${SDIR}/makeDocker.sh ${SDIR}/build/ica/makeDocker.sh - cp ${SDIR}/fabric.config ${SDIR}/build/ica/fabric.config - cp ${SDIR}/down-images.sh ${SDIR}/build/ica/down-images.sh - cp ${SDIR}/scripts/start-intermediate-ca.sh ${SDIR}/build/ica/scripts/start-intermediate-ca.sh - cp ${SDIR}/scripts/env.sh ${SDIR}/build/ica/scripts/env.sh - cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/ica/scripts/file_exits.sh - cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/ica/scripts/file_scp.sh + if $USE_INTERMEDIATE_CA; then + log "===> Package ICA files" + mkdir -p ${SDIR}/build/ica/scripts + cp ${SDIR}/ica-bootstrap.sh ${SDIR}/build/ica/ica-bootstrap.sh + cp ${SDIR}/makeDocker.sh ${SDIR}/build/ica/makeDocker.sh + cp ${SDIR}/fabric.config ${SDIR}/build/ica/fabric.config + cp ${SDIR}/down-images.sh ${SDIR}/build/ica/down-images.sh + cp ${SDIR}/scripts/start-intermediate-ca.sh ${SDIR}/build/ica/scripts/start-intermediate-ca.sh + cp ${SDIR}/scripts/env.sh ${SDIR}/build/ica/scripts/env.sh + cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/ica/scripts/file_exits.sh + cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/ica/scripts/file_scp.sh + + distriICA + fi ########################## 打包setup ########################## - log "===> Package SETUP files" + log "===> Package Setup files" mkdir -p ${SDIR}/build/setup/scripts cp ${SDIR}/setup-bootstrap.sh ${SDIR}/build/setup/setup-bootstrap.sh cp ${SDIR}/makeDocker.sh ${SDIR}/build/setup/makeDocker.sh @@ -74,8 +243,23 @@ function package { cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/setup/scripts/file_exits.sh cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/setup/scripts/file_scp.sh + distriSetup + + ########################## 打包zookeeper & kafka ########################## + log "===> Package Zookeeper & Kafka files" + mkdir -p ${SDIR}/build/zk/scripts + cp ${SDIR}/zk-kafka-bootstrap.sh ${SDIR}/build/zk/zk-kafka-bootstrap.sh + cp ${SDIR}/makeDocker.sh ${SDIR}/build/zk/makeDocker.sh + cp ${SDIR}/fabric.config ${SDIR}/build/zk/fabric.config + cp ${SDIR}/down-images.sh ${SDIR}/build/zk/down-images.sh + cp ${SDIR}/scripts/env.sh ${SDIR}/build/zk/scripts/env.sh + cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/zk/scripts/file_exits.sh + cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/zk/scripts/file_scp.sh + + distriZK + ########################## 打包orderer ########################## - log "===> Package ORDERER files" + log "===> Package Orderer files" mkdir -p ${SDIR}/build/orderer/scripts cp ${SDIR}/orderer-bootstrap.sh ${SDIR}/build/orderer/orderer-bootstrap.sh cp ${SDIR}/makeDocker.sh ${SDIR}/build/orderer/makeDocker.sh @@ -86,8 +270,10 @@ function package { cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/orderer/scripts/file_exits.sh cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/orderer/scripts/file_scp.sh + distriOrderer + ########################## 打包peer ########################## - log "===> Package PEER files" + log "===> Package Peer files" mkdir -p ${SDIR}/build/peer/scripts cp ${SDIR}/peer-bootstrap.sh ${SDIR}/build/peer/peer-bootstrap.sh cp ${SDIR}/makeDocker.sh ${SDIR}/build/peer/makeDocker.sh @@ -98,12 +284,28 @@ function package { cp ${SDIR}/scripts/file_exits.sh ${SDIR}/build/peer/scripts/file_exits.sh cp ${SDIR}/scripts/file_scp.sh ${SDIR}/build/peer/scripts/file_scp.sh + distriPeer + log "===> Construct a host configuration" echo # 构造host配置 { + COUNT=1 + while [[ "$COUNT" -le $NUM_ZOOKEEPER ]]; do + initZKVars $((COUNT-1)) + echo $(cat fabric.config | jq -r '.NET_CONFIG.ZOOKEEPER_CLUSTER['$((COUNT-1))'].IP') $ZK_HOST + COUNT=$((COUNT+1)) + done + + COUNT=1 + while [[ "$COUNT" -le $NUM_KAFKA ]]; do + initKafkaVars $((COUNT-1)) + echo $(cat fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].IP') $KAFKA_HOST + COUNT=$((COUNT+1)) + done + for ORG in $ORGS; do initOrgVars $ORG if $USE_INTERMEDIATE_CA; then diff --git a/fabric-ca/peer-bootstrap.sh b/fabric-ca/peer-bootstrap.sh index 233689e..1261628 100644 --- a/fabric-ca/peer-bootstrap.sh +++ b/fabric-ca/peer-bootstrap.sh @@ -75,8 +75,8 @@ ${SDIR}/makeDocker.sh # 创建peer docker容器 log "Creating docker containers $PEER_NAME ..." -docker-compose up -d --no-deps $PEER_NAME - +# docker-compose up -d --no-deps $PEER_NAME +docker-compose up -d $PEER_NAME # 等待'peer'容器启动,随后tail -f dowait "the docker 'peer' container to start" 60 ${SDIR}/${PEER_LOGFILE} ${SDIR}/${PEER_LOGFILE} diff --git a/fabric-ca/scripts/env.sh b/fabric-ca/scripts/env.sh index bf1b242..30bb2da 100644 --- a/fabric-ca/scripts/env.sh +++ b/fabric-ca/scripts/env.sh @@ -6,6 +6,12 @@ FABRIC_ROOT=$GOPATH/src/github.com/hyperledger/fabric +# zookeeper集群节点数量 +NUM_ZOOKEEPER=NUM_ZOOKEEPER_PLACEHOLDER + +# kafka集群节点数量 +NUM_KAFKA=NUM_KAFKA_PLACEHOLDER + # orderer组织的名称 ORDERER_ORGS=ORDERER_ORGS_PLACEHOLDER @@ -204,6 +210,30 @@ function initOrgVars { fi } +function initZKVars { + + if [ $# -ne 1 ]; then + echo "Usage: initZKVars " + exit 1 + fi + + ZK_HOST=zookeeper$1 + ZK_NAME=zookeeper$1 +} + +function initKafkaVars { + + if [ $# -ne 1 ]; then + echo "Usage: initKafkaVars " + exit 1 + fi + + KAFKA_ID=$1 + + KAFKA_HOST=kafka${KAFKA_ID} + KAFKA_NAME=kafka${KAFKA_ID} +} + # initOrdererVars function initOrdererVars { @@ -265,6 +295,9 @@ function initPeerVars { PEER_SUCCESS_FILE=$LOGDIR/${PEER_NAME}.success PEER_FAIL_FILE=$LOGDIR/${PEER_NAME}.fail + PEER_COUCHDB_NAME=couchdb${NUM}-${ORG} + PEER_COUCHDB_HOST=couchdb${NUM}-${ORG} + MYHOME=/opt/gopath/src/github.com/hyperledger/fabric/peer TLSDIR=$MYHOME/tls @@ -555,7 +588,7 @@ EOF # if [ $? -ne 0 ]; then # fatal "Failed to copy client tls certificate from remote Peer" # fi - ${SDIR}/scripts/file_scp.sh ${PEER_USER_NAME} ${PEER_IP} ${PEER_PWD} ${TLS_CLIENTCERT_REMOTE_FILE} "$PWD${TLS_CLIENTCERT_FILE}" >& ssh.log + ${SDIR}/scripts/file_scp.sh ${PEER_USER_NAME} ${PEER_IP} ${PEER_PWD} ${TLS_CLIENTCERT_REMOTE_FILE} "$PWD${TLS_CLIENTCERT_FILE}" "from" >& ssh.log rs=$? if [ $rs -eq 1 ]; then fatal "Failed to copy client tls certificate from remote Peer. exits?" @@ -638,7 +671,7 @@ EOF # if [ $? -ne 0 ]; then # fatal "Failed to copy certificate from remote CA" # fi - ${SDIR}/scripts/file_scp.sh ${CA_USER_NAME} ${CA_IP} ${CA_PWD} ${CACHAIN_REMOTE_FILE} "$PWD${CA_CHAINFILE}" >& ssh.log + ${SDIR}/scripts/file_scp.sh ${CA_USER_NAME} ${CA_IP} ${CA_PWD} ${CACHAIN_REMOTE_FILE} "$PWD${CA_CHAINFILE}" "from" >& ssh.log rs=$? if [ $rs -eq 1 ]; then fatal "Failed to copy certificate from remote CA. exits?" @@ -712,7 +745,7 @@ EOF # if [ $? -ne 0 ]; then # fatal "Failed to copy MSP from remote 'setup'" # fi - ${SDIR}/scripts/file_scp.sh ${SETUP_USER_NAME} ${SETUP_IP} ${SETUP_PWD} ${remoteOrgMsp} ${PWD}$(dirname "$ORG_MSP_DIR") >& ssh.log + ${SDIR}/scripts/file_scp.sh ${SETUP_USER_NAME} ${SETUP_IP} ${SETUP_PWD} ${remoteOrgMsp} ${PWD}$(dirname "$ORG_MSP_DIR") "from" >& ssh.log rs=$? if [ $rs -eq 1 ]; then fatal "Failed to copy MSP from remote 'setup'. exits?" @@ -783,7 +816,7 @@ EOF # if [ $? -ne 0 ]; then # fatal "Failed to copy channel configuration transaction from remote 'setup'" # fi - ${SDIR}/scripts/file_scp.sh ${SETUP_USER_NAME} ${SETUP_IP} ${SETUP_PWD} ${remoteChannelTxFile} "$PWD${CHANNEL_TX_FILE}" >& ssh.log + ${SDIR}/scripts/file_scp.sh ${SETUP_USER_NAME} ${SETUP_IP} ${SETUP_PWD} ${remoteChannelTxFile} "$PWD${CHANNEL_TX_FILE}" "from" >& ssh.log rs=$? if [ $rs -eq 1 ]; then fatal "Failed to copy channel configuration transaction from remote 'setup'. eixts?" @@ -884,6 +917,17 @@ function fatal { exit 1 # 错误退出 } +function installJQAuto { + + which jq >& /dev/null + if [ $? -ne 0 ]; then + log "Not installed jq" + echo "Installing jq" + # 使用-y选项会在安装过程中使用默认设置,如果默认设置为N,那么就会选择N,而不会选择y。并没有让apt-get一直选择y的选项。 + apt-get -y update && apt-get -y install jq + fi +} + function installJQ { set +e diff --git a/fabric-ca/scripts/file_delete.sh b/fabric-ca/scripts/file_delete.sh new file mode 100644 index 0000000..d9cd2de --- /dev/null +++ b/fabric-ca/scripts/file_delete.sh @@ -0,0 +1,76 @@ +#!/usr/bin/expect + +# 删除远程服务器的文件 +# +# 错误码: +# -1:语法错误 +# 0:文件不存在 | 删除成功 +# 1:文件不存在 +# 2:密码错误 + +if {$argc < 4} { + send_user "Usage: $argv0 " + exit -1 +} + +set timeout -1 + +set remote_user [lindex $argv 0] ;# 远程服务器用户名 +set remote_host [lindex $argv 1] ;# 远程服务器域名 +set remote_pwd [lindex $argv 2] ;# 远程服务器密码 +set remote_file [lindex $argv 3] ;# 远程服务器文件 + +set passwd_error 0 +set date [exec date "+%Y-%m-%d %H:%M:%S"] + +spawn ssh ${remote_user}@${remote_host} "test -e ${remote_file} && echo 'File exists' || echo 'File Not exists'" + +expect { + + "*assword:" { + if { ${passwd_error} == 1 } { + send_user "Password is wrong!~\n" + exit 2 + } + set passwd_error 1 + send "${remote_pwd}\n" + exp_continue + } + "*es/no)?*" { + send "yes\n" + exp_continue + } + "File exists" { + } + "File Not exists" { + exit 0 + } +} + +set passwd_error 0 + +spawn ssh ${remote_user}@${remote_host} "rm -r ${remote_file} && echo 'File Deleted' || echo 'File Not Deleted'" + +expect { + + "*assword:" { + if { ${passwd_error} == 1 } { + send_user "Password is wrong!~\n" + exit 2 + } + set passwd_error 1 + send "${remote_pwd}\n" + exp_continue + } + "*es/no)?*" { + send "yes\n" + exp_continue + } + "File Delete" { + exit 0 + } + "File Not Deleted" { + send_user "\n##### ${date} Please manually delete the remote ${remote_host} folder ${remote_file} \n" + exit 1 + } +} \ No newline at end of file diff --git a/fabric-ca/scripts/file_exits.sh b/fabric-ca/scripts/file_exits.sh index 0ed2d77..dc842bd 100644 --- a/fabric-ca/scripts/file_exits.sh +++ b/fabric-ca/scripts/file_exits.sh @@ -1,8 +1,9 @@ #!/usr/bin/expect # 判断远程服务器中的文件是否存在 - +# # 错误码: +# -1:语法错误 # 0:存在 # 1:不存在 # 2:密码错误 diff --git a/fabric-ca/scripts/file_scp.sh b/fabric-ca/scripts/file_scp.sh index 06be097..47f2527 100644 --- a/fabric-ca/scripts/file_scp.sh +++ b/fabric-ca/scripts/file_scp.sh @@ -1,14 +1,15 @@ #!/usr/bin/expect -# SCP拷贝远程服务器的文件 - +# SCP远程拷贝 +# # 错误码: +# -1:语法错误 # 0:拷贝成功 # 1:拷贝失败,文件不存在 # 2:密码错误 -if {$argc < 5} { - send_user "Usage: $argv0 " +if {$argc < 6} { + send_user "Usage: $argv0 " exit -1 } @@ -19,10 +20,18 @@ set remote_host [lindex $argv 1] ;# 远程服务器域名 set remote_pwd [lindex $argv 2] ;# 远程服务器密码 set remote_file [lindex $argv 3] ;# 远程服务器文件 set local_file [lindex $argv 4] ;# 保存的本地文件 +set direction [lindex $argv 5] ;# 拷贝的方向,可选值:from、to set passwd_error 0 -spawn scp -r ${remote_user}@${remote_host}:${remote_file} ${local_file} +if { ${direction} == "from" } { + spawn scp -r ${remote_user}@${remote_host}:${remote_file} ${local_file} +} elseif { ${direction} == "to" } { + spawn scp -r ${local_file} ${remote_user}@${remote_host}:${remote_file} +} else { + send_user "Usage: $argv0 " + exit -1 +} expect { diff --git a/fabric-ca/scripts/setup-fabric.sh b/fabric-ca/scripts/setup-fabric.sh index 4b56029..458a68c 100644 --- a/fabric-ca/scripts/setup-fabric.sh +++ b/fabric-ca/scripts/setup-fabric.sh @@ -154,7 +154,7 @@ Profiles: OrgsOrdererGenesis: Orderer: # orderer type:\"solo\" 、 \"kafka\" - OrdererType: solo + OrdererType: kafka Addresses:" # Orderers服务地址 for ORG in $ORDERER_ORGS; do local COUNT=1 @@ -177,10 +177,16 @@ Profiles: PreferredMaxBytes: 512 KB Kafka: # Brokers: Kafka brokers作为orderer后端 - # NOTE: 使用IP:port表示法 - Brokers: - - 127.0.0.1:9092 - Organizations:" # 属于orderer通道的组织 + # Note: 使用IP:port表示法 + Brokers:" + installJQAuto + COUNT=1 + while [[ "$COUNT" -le $NUM_KAFKA ]]; do + initKafkaVars $COUNT + echo " - "$(cat /fabric.config | jq -r '.NET_CONFIG.KAFKA_CLUSTER['$((COUNT-1))'].IP')":9092" + COUNT=$((COUNT+1)) + done + echo " Organizations:" # 属于orderer通道的组织 for ORG in $ORDERER_ORGS; do initOrgVars $ORG echo " - *${ORG_CONTAINER_NAME}" # 引用 diff --git a/fabric-ca/setup-bootstrap.sh b/fabric-ca/setup-bootstrap.sh index e410cfc..4771323 100644 --- a/fabric-ca/setup-bootstrap.sh +++ b/fabric-ca/setup-bootstrap.sh @@ -20,7 +20,7 @@ cat << EOF EOF echo " 如果脚本找不到,会基于fabric源码编译生成二进制文件,此时需要保证\"$HOME/gopath/src/github.com/hyperledger/fabric\"源码目录存在" echo -echo" 当然你也可以通过指定-d选项从网络下载该二进制文件" +echo " 当然你也可以通过指定-d选项从网络下载该二进制文件" } binariesInstall() { diff --git a/fabric-ca/zk-kafka-bootstrap.sh b/fabric-ca/zk-kafka-bootstrap.sh new file mode 100644 index 0000000..1cfd838 --- /dev/null +++ b/fabric-ca/zk-kafka-bootstrap.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright 凡派 All Rights Reserved. +# +# Apache-2.0 +# +# 启动Zookeeper 和 Kafka + +# 遇到错误退出 +set -e + +START_ZOOKEEPER=false +START_KAFKA=false + +function printHelp { + +cat << EOF + 使用方法: + zk-kafka-bootstrap.sh <-z|-k> [-?] + -h|-? 获取此帮助信息 + -z 启动zookeeper节点 + -k 启动kafka节点 +EOF +} + +while getopts "h?zk" opt; do + case "$opt" in + h|\?) + printHelp + exit 0 + ;; + z) + START_ZOOKEEPER=true + shift 1 + ;; + k) + START_KAFKA=true + shift 1 + ;; + esac +done + +if [ $# -ne 1 ]; then + echo "Usage: ./zk-kafka-bootstrap.sh [-z] [-k] " + exit 1 +fi + +ID=$1 + +SDIR=$(dirname "$0") +source ${SDIR}/scripts/env.sh +cd ${SDIR} + +# 创建docker-compose.yml文件 +${SDIR}/makeDocker.sh + +if ${START_ZOOKEEPER}; then + initZKVars $ID + # 启动zookeeper + log "Creating docker containers $ZK_NAME ..." + docker-compose up -d --no-deps $ZK_NAME + + docker logs -f $ZK_NAME +elif ${START_KAFKA}; then + initKafkaVars $ID + # 启动kafka + log "Creating docker containers $KAFKA_NAME ..." + docker-compose up -d --no-deps $KAFKA_NAME + + docker logs -f $KAFKA_NAME +fi \ No newline at end of file diff --git a/ica-tree.png b/ica-tree.png deleted file mode 100644 index 82526c0755546be1e35198b24f11627deb189b11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15160 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