OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（3/3）〜 分散環境解説編

この記事は、OSSの分散型台帳技術であるHyperledger/fabricを用いて簡易な入出金処理を作り、docker swarm modeで分散処理させてみる、という連載の3回目です。

最終回の今回は、docker swarmクラスタ上でHyperledger/fabricを分散動作させてみます。

1. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（1/3）〜 動作確認編](https://qiita.com/nmatsui/items/cbcb7807984c65694e46)
1. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（2/3）〜 chaincode解説編](https://qiita.com/nmatsui/items/9689982e799ccb1255ed)
1. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（3/3）〜 分散環境解説編](https://qiita.com/nmatsui/items/7eb4e214b628f96814a0)

# docker swarm modeとdocker-compose.yaml
2016/07にリリースされた1.12からdocker本体に組み込まれた[swarm mode](https://docs.docker.com/engine/swarm/)を用いることで、dockerだけで複数ノードからなるdockerクラスタを構築できるようになりました。また2017/01リリースの1.13からは、docker-composeのyaml定義ファイルからswarm上にサービス群をまとめてstackとして構築できるようになりました。

[GKE](https://cloud.google.com/kubernetes-engine/?hl=ja)や[AKS](https://azure.microsoft.com/ja-jp/services/container-service/)のみならず[EKS](https://aws.amazon.com/jp/eks/)も出てきた昨今、コンテナオーケストレーションプラットフォームとしては[kubernetes](https://kubernetes.io/)一択のような気がしないでもないのですが、とりあえずdocker-composeのyaml定義を流用でき、他のソフトウェアをインストールして環境構築しなくても複数ノードにコンテナを分散配置して協調動作した際の挙動を確認することができる、という意味では、docker swarm modeは便利です。ということで今回は、docker swarm modeを用いてHyperledger/fabricを分散動作させることにしました（言い訳おわり）。

## docker-complseのyaml定義をdocker swarm modeで実行する際の注意点
### ○ コンテナ名
docker-composeを用いて単体docker上でサービスを起動する場合、yaml定義ファイルの`service`の名前としてアルファベット大文字小文字、数字、ピリオド、ハイフン、アンダースコア（`[a-zA-Z0-9\._\-]`）を使うことができます。
yaml定義ファイルに`container_name`を定義していた場合はその名前でコンテナが立ち上がりますし、指定していなかった場合は`<<プロジェクト名>>_<<サービス名>>_<<連番>>`という形でコンテナ名が付与されます。
> 実際、Hyperledger/fabricの公式サンプルの[docker-compose.yaml](https://github.com/hyperledger/fabric-samples/blob/release/basic-network/docker-compose.yml)では、`ca.example.com`といった名前で認証局サービスが定義されています。

一方、docker-composeでは有効だったyaml定義をdocker swarm modeにdeployすると、コンテナ起動に失敗する場合があります。例えば上記の`ca.example.com`というサービス名で認証局コンテナを起動しようとすると、次のようなエラーが発生します。

```bash
failed to create service fabric-payment_ca.example.com: Error response from daemon: rpc error: code = InvalidArgument desc = name must be valid as a DNS name component
```
docker swarm modeでは、yaml定義ファイルに`container_name`を定義していても無視され、`<<スタック名>>_<<サービス名>>.<<連番>>.<<タスクID>>`という形式でコンテナ名を生成します。どうもこのコンテナ名が、正しいDNS名として認識できる形になっていないとダメなようです。

正しいDNS名となるためには、アンダースコアやピリオドは使わず、小文字（あるいは大文字）と数字とハイフンのみで、なるべく短めのサービス名にしたほうが良いでしょう。
> ピリオド(.)で区切られた部分は「ラベル」と呼ばれます。 一つのラベルの長さは63文字以下、ドメイン名全体の長さは、 ピリオドを含めて253文字以下でなければなりません。 ラベルには、英字(A～Z)、数字(0～9)、 ハイフン( - )が使用できます(ラベルの先頭と末尾の文字をハイフンとするのは不可)。 ラベル中では大文字・小文字の区別はなく、 同じ文字とみなされます。
>出典：https://www.nic.ad.jp/ja/dom/system.html

### ○ ホスト名
docker-composeを用い単体docker上でHyperledger/fabricネットワークを起動する場合、bridge networkが自動作成（手動で作成しても良いですが）され内部DNSによりサービス名で名前解決されます。そのため特に何も設定をしなくても、コンテナ間はサービス名で通信できます。

swarm modeで分散環境下でHyperledger/fabricネットワークを起動する場合は、bridge networkではなくnodeをまたがって仮想ネットワークを敷設できるoverlay networkを利用することになります。

docker-composeのyaml定義に従ってコンテナを起動する上ではこれで何も問題は起きないのですが、第一回で説明したように、Hyperledger/fabricはpeerコンテナ内から直接chaicodeコンテナを起動し、docker networkに接続しようとします。この処理の最中、peerがdockerの内部dnsをlookupしているようなのですが、docker swarm modeの場合はなぜか自動生成するホスト名だとlookupがtimeoutしてしまうようです（詳細は追ってません。。。）。
docker-composeのyaml定義に、サービス名と同じホスト名を明示的に指定してあげることで、この問題を回避することができます。

### ○ overlay network名
docker-composeのyaml定義にoverlay driverを指定した`networks`エントリを記述すると、docker swarm modeにdeployした際にそのstack用のoverlay networkが自動作成されます。docker-composeのyaml定義に定義されているコンテナを起動してoverlay networkで接続する上では何も問題は起きませんし、stack削除時にはそのoverlay networkも自動的に削除されるため便利なのですが、Hyperledger/fabricで用いるためには二点注意が必要です。

* overlay networkはattachableでなければならない
    * peerコンテナが起動したchaicodeコンテナをoverlay networkに接続するため、overlay networkには後からコンテナを接続できなければなりません。driverを指定しただけでは自動作成されたoverlay networkはattachableになりませんので、`networks`エントリで明示的に`attachable: true`を指定する必要があります（attachable指定は、yaml定義ファイルのversionは3.3から導入） 
* peerコンテナに設定する`CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE`環境変数の値
    * この環境変数には、chaincodeを接続するdocker networkの名前を設定します。docker swarm modeへのdeploy時に自動起動したoverlay networkは、`<<スタック名>>_<<yaml定義中で設定したnetwork名>>`という名前になりますので、network名をそのまま指定するとchaincodeコンテナが立ち上がりません。

今回のサンプルでは、zookeeperやkafkaのstackを分割したこともあり、事前に作成したattacableなoverlay networkを参照する形に落ち着きました（stackを削除しても残るため、最後にoverlay networkを自分で削除しなければなりませんが）。

### ○ コンテナを起動するnodeの指定
Hyperledger/fabricはordererやpeer、couchdb等が緊密に連携して動作しますので、連携したコンテナは適切なnodeで動作させる必要があります（ordererが全て同一nodeで動作していたら耐障害性がありませんし、peerが使用するcouchdbが別々のnodeで動作していたらレイテンシ的にもネットワーク負荷的にも不利です）。
そこでversion3以降のyaml定義に導入された`deploy`指定を用い、連携したコンテナ群を適切なnodeに配置できるようにします。

```yaml
version: '3'

networks:
  fabric-sample-nw:
    external: true

services:
...
  peer0:
    hostname: peer0
    image: localhost:5000/fabric-payment-swarm/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/
      - CORE_PEER_ADDRESS=peer0:7051
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric-sample-nw
      - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0:5984
      - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=
      - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=
    working_dir: /etc/hyperledger
    command: peer node start
    ports:
      - 7051
      - 7053
    volumes:
      - /var/run/docker.sock:/host/var/run/docker.sock
    networks:
      - fabric-sample-nw
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.type == node0
...
```

# docker swarmクラスタを用い分散環境下で動かしてみよう
上記の注意点を踏まえ、node0〜node3の4台のVM上で動作するdocker swarmクラスタ上で、Hyperledger/fabricを分散動作させてみましょう。以下のようなコンテナ群でHyperledger/fabricネットワークを構成します。

![swarm.png](https://qiita-image-store.s3.amazonaws.com/0/37648/c4f33ae1-2b07-103b-77c8-d99b61d26deb.png)

## zookeeperとkafka
分散環境下でordererを協調動作させるためには、最低限４つのkafkaノードと、3つ以上かつ奇数のzookeeperノードが必要です。（[Bringing up a Kafka-based Ordering Service](http://hyperledger-fabric.readthedocs.io/en/release-1.1/kafka.html)）

同一ノードでkafkaを二つ動かしても耐障害性的に意味がないので、Hyperledger/fabricを分散環境下で動作させるには最低限4つ以上のノードが必要ということになります。

## 認証局（ca）の可用性
今回のHyperledger/fabricネットワークは、認証局（ca）がSPOFとなっています。kafkaとzookeeperで分散協調動作してくれるordererとは違い、認証局は耐障害性の高いRDBMS（PostgreSQLかMySQL）あるいはLDAPをバックエンドに、自力で負荷分散とフェイルオーバーを組む必要があります。

![fabric-ca.png](http://hyperledger-fabric-ca.readthedocs.io/en/latest/_images/fabric-ca.png)
出典：http://hyperledger-fabric-ca.readthedocs.io/en/latest/users-guide.html#prerequisites

今回は力尽きましたので、認証局の可用性は別の機会としたいと思います。。。


## docker swarm modeクラスタを起動する
1. node0でswarmモード用のディレクトリに移動する

    ````bash
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ cd ../swarm-kafka/
    ```

1. node0でswarmクラスタを立ち上げる
    * 今回検証したVirtualboxVMには仮想NICが2枚刺さっているため、--advertise-addrでdocker swarmが使用する仮想NICのIPアドレスを指定します。

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker swarm init --advertise-addr 192.168.100.10
    ````
    * `docker swarm init`を実行すると、他のnodeをworkerとして追加するためのTOKENが表示されますが、今回は使いません。

1. masterとしてswarmクラスタにjoinするためのTOKENを表示する

    ````bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker swarm join-token manager
    ````
    * 次のようなコマンド文字列が表示されます  
    `docker swarm join --token SWMTKN-1-..... 192.168.100.10:2377`

1. 3.で表示されたコマンドを**node1**、**node2**、及び**node3**で実行し、managerとして各々をswarmクラスタに参加させる

    ```bash
    ubuntu@node1:~$ docker swarm join --token SWMTKN-1-...... 192.168.100.10:2377
    ```
    ```bash
    ubuntu@node2:~$ docker swarm join --token SWMTKN-1-...... 192.168.100.10:2377
    ```
    ```bash
    ubuntu@node3:~$ docker swarm join --token SWMTKN-1-...... 192.168.100.10:2377
    ```
1. 各nodeにラベルを付与する
    * yaml定義にてコンテナを立ち上げるnodeを指定できるようにするために、各nodeにラベルを付与します。
        * ホスト名で制約を指定することもできるので、今回の検証程度ならばラベルを付与しなくても動作させることは可能です。

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    n8aex03epr54vk0js3i968j7e *   node0               Ready               Active              Leader
    klg2t4ho5v47osx7mckb2ngyt     node1               Ready               Active              Reachable
    krwzoozg3l86l6ahvbhye5s8h     node2               Ready               Active              Reachable
    q31j94lguqvox13o5u84tsvvk     node3               Ready               Active              Reachable
    ```
    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker node update --label-add type=node0 n8aex03epr54vk0js3i968j7e
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker node update --label-add type=node1 klg2t4ho5v47osx7mckb2ngyt
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker node update --label-add type=node2 krwzoozg3l86l6ahvbhye5s8h
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker node update --label-add type=node3 q31j94lguqvox13o5u84tsvvk
    ```

## swarmクラスタ内にローカルレジストリを起動する
1. ローカルレジストリをswarmのサービスとして立ち上げる

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker service create --name registry --publish 5000:5000 registry:2
    ```
    * ビルドしたdocker imageを分散環境下の各ノードで共有できるように、swarmクラスタ内にローカルレジストリを立ち上げます。
        * peerがトランザクションに署名する暗号鍵など、Hyperledger/fabricネットワークの管理者以外には秘匿すべき情報を焼き込んだdocker imageを生成するため、DockerHub等のパブリックレジストリは使用せず、ローカルレジストリに登録します。
        * プライベートなDockerRegistryサービスが既にあるならば、そちらを使っても良いでしょう。



## NFSを設定する
1. node0にNFSサーバを立ち上げる
    * 分散環境下ではREST APIコンテナが複数ノードで立ち上がることになるため、Hyperledger/fabricネットワークの認証局に接続するための暗号鍵ファイルを各REST APIコンテナで共有しなければなりません。
    * またpeerをchannelにjoinさせる際にchannelをcreateした際に生成されるgenesisファイルが必要となるため、ノード障害等でgenesisファイルを失うと新たなpeerのjoinが困難になります。
    * 上記のファイルは常時読み書きするものではないため、NFSで共有することにします。

    ```bash:例）node0（192.168.100.10）の/opt/nfs/fabric-paymentをNFSで共有する場合
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo apt-get install nfs-kernel-server -y
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo mkdir -p /opt/nfs/fabric-payment/key_store
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo mkdir -p /opt/nfs/fabric-payment/genesis_store
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo su -c "echo '/opt/nfs/fabric-payment 192.168.100.0/24(rw,sync,no_subtree_check,no_root_squash)' >> /etc/exports"
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo exportfs -ra
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ sudo systemctl restart nfs-kernel-server.service
    ```
    * 今回は手っ取り早くnode0にNFSサーバを立ち上げましたが、既に運用されているNFSサービスがあるならば、それを利用したほうが楽かもしれません。

1. node1〜node3にNFSクライアントライブラリを導入する

    ```bash
    ubuntu@node1:~$ sudo apt-get install nfs-common -y
    ```
    ```bash
    ubuntu@node2:~$ sudo apt-get install nfs-common -y
    ```
    ```bash
    ubuntu@node3:~$ sudo apt-get install nfs-common -y
    ```

## swarmクラスタ上にHyperledger/fabricのネットワークを起動する
1. 第一回を参考に、以下の手順を実行する
    1. dockerのインストール
    2. githubから`fabric-payment-sample-docker`と`fabric-payment-sample-api`をclone
    3. chaincodeをgo get
    4. REST APIアプリのBearer tokenを生成し、Docker imageを生成

1. `fabric-payment-sample-docker/swarm-kafka`でHyperledger/fabric 1.1.0-rc1のバイナリとdocker imageを取得する

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ curl -sSL https://goo.gl/6wtTN5 | bash -s 1.1.0-rc1
    ```

1. 環境変数を設定した後、Hyperledger/fabricが必要とする暗号鍵と、fabricネットワークの設定等が記載されているアーティファクトを生成する

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ source .env
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ export CA_ADMIN_PASSWORD=<<任意の文字列>>
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ ./generate_artifact.sh ${CA_ADMIN_PASSWORD} <<NFSサーバのIP>> <<共有ディレクトリ>>
    ```
    * docker単体の場合と異なり、NFSの設定を引数で与えます。
        * この`generate_artifact.sh`は環境毎に異なる変数を埋め込んだdocker-compose.yamlを生成しますが、その際にNFSを用いた共有volumeも定義するためです。
    * 自動生成した各コンテナが署名に使う暗号鍵やchaincode、REST APIのソースコードなどを焼き込んだdocker imageも生成し、ローカルリポジトリに登録します。詳細は[Dockerfile](https://github.com/nmatsui/fabric-payment-sample-docker/tree/master/swarm-kafka/swarm-dockerfiles)を確認してください。

1. Hyperledger/fabricネットワークを分散環境下で立ち上げる

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ ./start_fabric.sh
    ```
    * docker単体の場合と異なり、swarm環境下で複数のnodeに分散させてコンテナを立ち上げるには、時間がかかる場合があります。
    * そのため、`start_fabric.sh`ではコンテナ起動まで行います。全てのコンテナが立ち上がっている(CURRENT STATEがRunningになっている）ことを確認してください。
        * 特に初回は各nodeがローカルリポジトリからdocker imageをpullする手間がかかるため、zookeeperやkafkaが居ない状態でorderderの起動を試みて、起動に失敗する可能性があります。

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker stack ps zookeeper
    ID                  NAME                     IMAGE                                                NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
    jjn0hqts9w3t        zookeeper_zookeeper2.1   localhost:5000/hyperledger/fabric-zookeeper:latest   node2               Running             Running about a minute ago
    wwtfc54krest        zookeeper_zookeeper1.1   localhost:5000/hyperledger/fabric-zookeeper:latest   node1               Running             Running about a minute ago
    6pl9wohamgfl        zookeeper_zookeeper0.1   localhost:5000/hyperledger/fabric-zookeeper:latest   node0               Running             Running about a minute ago
    ```
    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker stack ps kafka
    ID                  NAME                IMAGE                                            NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
    ab0y5b66esy4        kafka_kafka1.1      localhost:5000/hyperledger/fabric-kafka:latest   node1               Running             Running about a minute ago
    f65rcxrzou10        kafka_kafka0.1      localhost:5000/hyperledger/fabric-kafka:latest   node0               Running             Running about a minute ago
    355ff4e4j0rf        kafka_kafka3.1      localhost:5000/hyperledger/fabric-kafka:latest   node3               Running             Running about a minute ago
    123u0483b4lh        kafka_kafka2.1      localhost:5000/hyperledger/fabric-kafka:latest   node2               Running             Running about a minute ago
    ```
    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ docker stack ps ${STACK_NAME}
ID                  NAME                                           IMAGE                                                       NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
krfhlqfh3vvz        fabric-payment_cli.krwzoozg3l86l6ahvbhye5s8h   localhost:5000/fabric-payment-swarm/fabric-tools:latest     node2               Running             Running 38 seconds ago
1ctlrvha8ecv        fabric-payment_cli.q31j94lguqvox13o5u84tsvvk   localhost:5000/fabric-payment-swarm/fabric-tools:latest     node3               Running             Running 36 seconds ago
wcww4fuohsdi        fabric-payment_cli.klg2t4ho5v47osx7mckb2ngyt   localhost:5000/fabric-payment-swarm/fabric-tools:latest     node1               Running             Running 3 minutes ago
l9wp44p2mfub        fabric-payment_cli.n8aex03epr54vk0js3i968j7e   localhost:5000/fabric-payment-swarm/fabric-tools:latest     node0               Running             Running 3 minutes ago
tu1jc0fb1ec0        fabric-payment_peer3.1                         localhost:5000/fabric-payment-swarm/fabric-peer:latest      node3               Running             Running 2 minutes ago
s3h5oovqe4hk        fabric-payment_api3.1                          localhost:5000/fabric-payment-swarm/api:latest              node3               Running             Running about a minute ago
4ai3nchy19b3        fabric-payment_peer0.1                         localhost:5000/fabric-payment-swarm/fabric-peer:latest      node0               Running             Running 3 minutes ago
jivvxdhxxia6        fabric-payment_api0.1                          localhost:5000/fabric-payment-swarm/api:latest              node0               Running             Running 3 minutes ago
16zcfd3qhwkl        fabric-payment_api1.1                          localhost:5000/fabric-payment-swarm/api:latest              node1               Running             Running 3 minutes ago
ln15z8ho5t92        fabric-payment_peer2.1                         localhost:5000/fabric-payment-swarm/fabric-peer:latest      node2               Running             Running 2 minutes ago
xqz7ppa7a4if        fabric-payment_couchdb3.1                      localhost:5000/hyperledger/fabric-couchdb:latest            node3               Running             Running 34 seconds ago
i8p08cu3hgjk        fabric-payment_couchdb0.1                      localhost:5000/hyperledger/fabric-couchdb:latest            node0               Running             Running 3 minutes ago
ujchvelk2hek        fabric-payment_couchdb1.1                      localhost:5000/hyperledger/fabric-couchdb:latest            node1               Running             Running 3 minutes ago
ye7pcr7g4o8s        fabric-payment_couchdb2.1                      localhost:5000/hyperledger/fabric-couchdb:latest            node2               Running             Running 46 seconds ago
27ugkqwdl9yk        fabric-payment_orderer3.1                      localhost:5000/fabric-payment-swarm/fabric-orderer:latest   node3               Running             Running 2 minutes ago
hhafwq7dweux        fabric-payment_orderer2.1                      localhost:5000/fabric-payment-swarm/fabric-orderer:latest   node2               Running             Running 2 minutes ago
qyysocbzi9nu        fabric-payment_api2.1                          localhost:5000/fabric-payment-swarm/api:latest              node2               Running             Running about a minute ago
qoqhmzt7oav0        fabric-payment_peer1.1                         localhost:5000/fabric-payment-swarm/fabric-peer:latest      node1               Running             Running 3 minutes ago
yzv2v2z2j84y        fabric-payment_orderer1.1                      localhost:5000/fabric-payment-swarm/fabric-orderer:latest   node1               Running             Running 3 minutes ago
fxw6ld1dlrg6        fabric-payment_orderer0.1                      localhost:5000/fabric-payment-swarm/fabric-orderer:latest   node0               Running             Running 3 minutes ago
0y1rsvglu7u7        fabric-payment_ca.1                            localhost:5000/fabric-payment-swarm/fabric-ca:latest        node0               Running             Running 3 minutes ago
    ```
    * zookeeper, kafka, 今回のアプリとstackが別れているのは、docker-compose設定ファイルからswarm stackを起動する場合、stack内でのコンテナの起動順序が制御できないからです。
        * docker-composeを用いて単体dockerの中で起動する場合、`depends_on`でコンテナ起動順序を制御できますが、swarm modeではサポートされていません。

        > The depends_on option is ignored when deploying a stack in swarm mode with a version 3 Compose file.
        > https://docs.docker.com/compose/compose-file/#depends_on

1. Hyperledger/fabricネットワークの設定を行う

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ ./setup_fabric.sh
    ```
    * `setup_fabric.sh`では、Hyperledger/fabricネットワークを利用可能にするために次の処理を行っています。
        * 1) channelの生成

        ```bash
        docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" ${CLI} peer channel create -o ${ORDERER_ADDRESS} -c ${CHANNEL_NAME} -f /etc/hyperledger/artifacts/channel.tx
        ```
        * 2) 生成されたchannelのgenesisファイルをnfsへバックアップ
        ```bash
        docker exec ${CLI} cp /etc/hyperledger/artifacts/${CHANNEL_NAME}.block /etc/hyperledger/genesis_store/
        ```

        * 3) peer0〜peer4の各コンテナをchannelへjoinさせ、chaincodeをインストール
        
        ```bash
        setup_peer () {
          docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${1}" ${CLI} peer channel join -b /etc/hyperledger/artifacts/${CHANNEL_NAME}.block

          docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${1}" ${CLI} peer chaincode install -n ${CHAINCODE_NAME} -p ${CHAINCODE_SRC} -v ${CHAINCODE_VERSION}
}
        setup_peer "peer0:7051"
        setup_peer "peer1:7051"
        setup_peer "peer2:7051"
        setup_peer "peer3:7051"
        ```
        * 4) peerのancher情報をchannelへ登録

        ```bash
        PEER_ADDRESS="peer0:7051"
        docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" ${CLI} peer channel update -o ${ORDERER_ADDRESS} -c ${CHANNEL_NAME} -f /etc/hyperledger/artifacts/Org1MSPanchors.tx
        ```
        * 5) chaincodeの初期化

        ```bash
        PEER_ADDRESS="peer0:7051"
        docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" ${CLI} peer chaincode instantiate -o ${ORDERER_ADDRESS} -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -v ${CHAINCODE_VERSION} -c '{"Args":[""]}' -P "OR ('Org1MSP.member')"
        ```

* **注意**
    * chaincodeの初期化を行ったpeer（今回のスクリプトではpeer0）以外のREST APIは、初回呼び出しに時間がかかります。これはJustInTimeで新たにコンパイル用コンテナを立ち上げてpeerにインストールされているchaincodeをコンパイルし、その後chaincode用のコンテナを立ち上げるためです。
        * 初回呼び出しの前後で `docker ps` でコンテナリストを確認すると、 `dev-peer1.org1.example.com-fabric-payment-0.1-.....` のような名前のchaincode用コンテナが増えていることがわかります。

## Hyperledger/fabricの分散処理を確認する
1. 登録した口座を他のnodeで確認する
    * 単体dockerの場合と同様のREST APIが利用可能です。
        * node0で動作しているREST APIはポート3000です。同様に、node1はポート3001、node2は3002、node3は3003です。
    * dockerのoverlay networkによって仮想ネットワークが延伸されているため、他のnodeのREST APIを利用することも可能です。
        * 例えばnode0からnode1上のREST API（ポート3001）を起動するなど。

    ```bash:例）node0で口座を登録
    ubuntu@node0:~$ cat ${API_PATH}/api/.config/token.json | jq .token -r
4302E85F23783F132740B8A701B93534
ubuntu@node0:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3000/fabric-payment/accounts/ -X POST -d '{"name":"ほげ"}' | jq .
    {
      "model_type": "account",
      "no": "6107511000263813",
      "name": "ほげ",
      "balance": 0
    }
    ```
    ```bash:例）node1で口座一覧を取得
    ubuntu@node1:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3001/fabric-payment/accounts/ | jq .
    [
      {
        "model_type": "account",
        "no": "6107511000263813",
        "name": "ほげ",
        "balance": 0
      }
    ]
    ```
    * あるAPIノード経由で登録・更新した情報は、Hyperledger/fabricネットワーク内の全てのpeerに配信されます。そのためコミットに成功したら、リアルタイムに別のnodeのREST APIからも参照できます。

## Hyperledger/fabricの障害復旧を確認する
1. node0で障害が発生し、Hyperledger/fabricネットワークから切り離される
    * node0のdockerプロセスを停止すると、node0はswarmクラスタから除外されます。新たなLeaderノードが選出され、swarmクラスタはnode1〜node3で継続して動作します。

    ```bash
    ubuntu@node0:~$ sudo systemctl stop docker.service
    ```
    ```bash
    ubuntu@node1:~$ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    n8aex03epr54vk0js3i968j7e     node0               Down                Active              Unreachable
    klg2t4ho5v47osx7mckb2ngyt *   node1               Ready               Active              Reachable
    krwzoozg3l86l6ahvbhye5s8h     node2               Ready               Active              Leader
    q31j94lguqvox13o5u84tsvvk     node3               Ready               Active              Reachable
    ```

1. 口座を追加する
    * nodeが一つダウンしていても、Hyperledger/fabricネットワークは動作しています。node3からもう一つ口座を登録してみます。

    ```bash
    ubuntu@node3:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3003/fabric-payment/accounts/ -X POST -d '{"name":"ふが"}' | jq .
    {
      "model_type": "account",
      "no": "9962914372253968",
      "name": "ふが",
      "balance": 0
    }
    ```
    ```bash
    ubuntu@node3:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3003/fabric-payment/accounts/ | jq .
    [
      {
        "model_type": "account",
        "no": "6107511000263813",
        "name": "ほげ",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "9962914372253968",
        "name": "ふが",
        "balance": 0
      }
    ]
    ```

1. node0の障害を回復させる
    * node0の障害が回復すると、swarmクラスタが検知してswarmクラスタに自動的にjoinします。縮退動作していたstackも復旧し、必要なコンテナがnode0で自動的に立ち上がります。

    ```bash:障害回復直後
    ubuntu@node0:~$ sudo systemctl start docker.service
    ubuntu@node0:~$ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES 
    ```
    ```bash:swarmクラスタへの自動復帰後
    ubuntu@node0:~$ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    6k32ss9rdtfe6c0e0m6xf979y *   node0               Ready               Active              Reachable
    62retjrvcwkeky1q0mqveojvj     node1               Ready               Active              Reachable
    izk1hvipbfuhoedhdx6dmh5ri     node2               Ready               Active              Leader
    09w52jcgw2u4g5mjt0t1qe39b     node3               Ready               Active              Reachable
    
    ubuntu@node0:~$ docker ps
    CONTAINER ID        IMAGE                                                       COMMAND                  CREATED             STATUS              PORTS                          NAMES
0ec086151a3f        localhost:5000/fabric-payment-swarm/api:latest              "npm start"              49 seconds ago      Up 35 seconds                                      fabric-payment_api0.1.swhbo2iqrwgphku8mcson7nbq
b566619f22e0        localhost:5000/fabric-payment-swarm/fabric-ca:latest        "sh -c 'fabric-ca-se…"   49 seconds ago      Up 33 seconds       7054/tcp                       fabric-payment_ca.1.p29wism0liy4b0iuud8qkamfs
313356c1b5a9        localhost:5000/hyperledger/fabric-zookeeper:latest          "/docker-entrypoint.…"   49 seconds ago      Up 33 seconds       2181/tcp, 2888/tcp, 3888/tcp   zookeeper_zookeeper0.1.390um287fl0eywo1gdeu0v4wp
88f896299029        localhost:5000/fabric-payment-swarm/fabric-orderer:latest   "orderer"                49 seconds ago      Up 32 seconds       7050/tcp                       fabric-payment_orderer0.1.ogyc9a76hkwntx18vr6qgyhix
e916f36bc41e        localhost:5000/fabric-payment-swarm/fabric-peer:latest      "peer node start"        49 seconds ago      Up 36 seconds                                      fabric-payment_peer0.1.huz6q7x4763ky3jjtdlnqcohc
4e6a18cce499        localhost:5000/hyperledger/fabric-kafka:latest              "/docker-entrypoint.…"   50 seconds ago      Up 32 seconds       9092-9093/tcp                  kafka_kafka0.1.vqvd3el46ce23l87jauhu0sbf
eef8f7db4258        localhost:5000/fabric-payment-swarm/fabric-tools:latest     "/bin/bash"              50 seconds ago      Up 37 seconds                                      fabric-payment_cli.yufr9amrmtml1b1owsvmnkb2z.k5p5vub6k3y8vtkh0jhp4msok
b56170b724e6        localhost:5000/hyperledger/fabric-couchdb:latest            "tini -- /docker-ent…"   51 seconds ago      Up 29 seconds       4369/tcp, 5984/tcp, 9100/tcp   fabric-payment_couchdb0.1.tu2qy3xbiads9bez5yhqt1ecu
    ```

1. node0のコンテナ群をHyperledger/fabricネットワークに参加
    * 残念ながら、Hyperledger/fabricネットワークへは自動復帰できません。稼働中のchannelにjoinしてchaincodeをインストールする必要があります。
    * 1) 変数定義

    ```bash
    ubuntu@node0:~$ ORDERER_ADDRESS="orderer0:7050"
    ubuntu@node0:~$ PEER_ADDRESS="peer0:7051"
    ubuntu@node0:~$ LOCALMSPID="Org1MSP"
    ubuntu@node0:~$ PEER_MSPCONFIGPATH="/etc/hyperledger/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp"
    ubuntu@node0:~$ CHANNEL_NAME="fabric-sample"
    ubuntu@node0:~$ CHAINCODE_SRC="github.com/nmatsui/fabric-payment-sample-chaincode"
    ubuntu@node0:~$ CHAINCODE_VERSION="0.1"
    ubuntu@node0:~$ CLI=$(docker ps -f name=fabric-payment_cli -q)
    ```
    * 2) nfsからchannelのgenesisファイルを復旧

    ```bash
    ubuntu@node0:~$ docker exec ${CLI} cp /etc/hyperledger/genesis_store/${CHANNEL_NAME}.block /etc/hyperledger/artifacts
    ```
    * 3) channelへjoin

    ```bash
    ubuntu@node0:~$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" ${CLI} peer channel join -b /etc/hyperledger/artifacts/${CHANNEL_NAME}.block
    ```
    * 4) chaincodeをinstall

    ```bash
    ubuntu@node0:~$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" ${CLI} peer chaincode install -n ${CHAINCODE_NAME} -p ${CHAINCODE_SRC} -v ${CHAINCODE_VERSION}
    ```

1. node0で口座一覧を取得する
    * 　channelにjoinしたことで、state dbは障害発生後のトランザクションにも追随した状態になっています。

    ```bash
    ubuntu@node0:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3000/fabric-payment/accounts/ | jq .
    [
      {
        "model_type": "account",
        "no": "6107511000263813",
        "name": "ほげ",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "9962914372253968",
        "name": "ふが",
        "balance": 0
      }
    ]
    ```

1. node0で口座をさらに一つ追加する
    * ordererも動作しているので、node0での台帳更新も可能になっています。

    ```bash
    ubuntu@node0:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3000/fabric-payment/accounts/ -X POST -d '{"name":"ぴよ"}' | jq .
    {
      "model_type": "account",
      "no": "6028010551522005",
      "name": "ぴよ",
      "balance": 0
    }
    ```
    ```bash
    ubuntu@node1:~$ curl -H "Content-Type: application/json" -H "Authorization: Bearer 4302E85F23783F132740B8A701B93534" http://localhost:3001/fabric-payment/accounts/ | jq .
    [
      {
        "model_type": "account",
        "no": "6028010551522005",
        "name": "ぴよ",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "6107511000263813",
        "name": "ほげ",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "9962914372253968",
        "name": "ふが",
        "balance": 0
      }
    ]
    ```

## Hyperledger/fabricのネットワークを終了する
1. Hyperledger/fabricネットワークを終了する

    ```bash
    ubuntu@node0:~/fabric-payment-sample-docker/swarm-kafka$ ./terminate.sh <<NFSサーバのIP>> <<共有ディレクトリ>>
    ```
    * docker単体の場合と異なり、NFSの設定を引数で与えます。
        * 今回のスクリプトや設定ファイルでは、hyperledger/fabricの認証局の情報はコンテナ削除と同時に消滅します。そのため、認証用暗号鍵ファイルが残っていると、Hyperledger/fabricネットワークを再度立ち上げた時に認証エラーが発生します。これを防ぐために、`terminate.sh`内でNFSに保管されている暗号鍵ファイルを消去しています。
    * node1のdockerに、stopされているがrmされていないコンテナが残る場合があります。これはchaincode用コンテナなど、swarm stack管理下に無いコンテナはstackを削除してもrmできないからです。

# 最後に
無事にdocker swarmクラスタ上でHyperledger/fabricネットワークを分散動作させることができました。時間があれば、kubernetesでも試してみたいですね！