Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(使い方編)

[cloudpack あら便利カレンダー 2019](https://benri2019.cloudpack.jp/) の記事となります。[誕生秘話](https://qiita.com/shinichi-takahashi/items/cea235795f904ccea207#%E3%81%82%E3%82%89%E4%BE%BF%E5%88%A9%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E8%AA%95%E7%94%9F%E7%A7%98%E8%A9%B1) はこちら。




ブロックチェーンネットワークをフルマネージドで構築・運用できるAmazon Managed Blockchain(AMB)ですが、実際にネットワーク構築するにはそこそこの手順と時間がかかります。

検証などで頻繁に構築していると、いい加減に手間になってきたのでAWS CloudFormation(CFn)を用いてさくっとHyperledger Fabricのブロックチェーンネットワークとクライアント(EC2インスタンス)を構築、ついでにブロックチェーンネットワークにサンプル用のチャネルやチェーンコードをデプロイして、スタック作成後すぐに利用できるようしてみました。
せっかくCFnを利用するのでAMBリソースの削除にも対応しています。

CFnでテンプレート作成するのは楽しいですがキリがないので困ります^^

AWS CLIでブロックチェーンネットワークを構築する手順は下記が参考になります。

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e


## 使い方

AWS CLIを用いてスタック作成する手順となります。

### 前提

- AWSアカウントがある
- AWSアカウントに以下の作成権限がある
  - CFnスタック
  - インターフェイスVPCエンドポイント
  - セキュリティグループ
  - AMBネットワーク
  - EC2インスタンス
  - EC2キーペア
- AWS CLIがインストールされている
  - `aws configure` コマンドでアカウント設定済み
- `git` コマンドが利用可能
- `pip` コマンドが利用可能


### 準備

#### CFnテンプレートのダウンロード

GitHubにCFnのテンプレートをアップしていますのでそれを利用します。

kai-kou/amazon-managed-blockchain-cfn-template
https://github.com/kai-kou/amazon-managed-blockchain-cfn-template

本テンプレートで作成するリソースは以下となります。CFnがAMBのリソース管理に対応していないので(2019/07/02時点)、AWS Lambda-backedカスタムリソースも利用しています。

- AMB(カスタムリソース)
  - ネットワーク
  - メンバー
  - PeerNode
- セキュリティグループ
  - AMBネットワーク用
  - Hyperledger Fabricクライアント用
- インターフェイスVPCエンドポイント
- EC2インスタンス

```console
> git clone https://github.com/kai-kou/amazon-managed-blockchain-cfn-template.git
> cd amazon-managed-blockchain-cfn-template
```

#### EC2キーペアの作成

Hyperledger FabricのクライアントとしてEC2インスタンスを作成するためにキーペアを作成します。既存のキーペアを利用するのでもOKです。

```console
> aws ec2 create-key-pair \
  --key-name amb-client-ec2-key \
  --query "KeyMaterial" \
  --output text > amb-client-ec2-key.pem

> chmod 400 amb-client-ec2-key.pem
```

#### 最新のAWS SDK(boto3)をLambda Layerに登録する

AWS Lambda-backedカスタムリソースのLambda関数で利用できるAWS SDKはAMBがサポートされていないバージョンなので(2019/07/02時点)、Lambda Layersを利用して最新のAWS SDK(boto3)が利用できるようにします。詳細は下記をご参考ください。

AWS CloudFormationのAWS Lambda-backedカスタムリソースで最新のAWS SDKを利用する - Qiita
https://qiita.com/kai_kou/items/db0924f8bbd0fd03eb94


```console
> mkdir python
> pip install -t ./python boto3
(略)
Successfully installed boto3-1.9.180 botocore-1.12.180 docutils-0.14 jmespath-0.9.4 python-dateutil-2.8.0 s3transfer-0.2.1 six-1.12.0 urllib3-1.25.3

> zip -r python.zip ./python

> aws lambda publish-layer-version \
  --layer-name amb-boto3 \
  --zip-file fileb://python.zip \
  --compatible-runtimes python3.7

{
    "Content": {
        "Location": "https://prod-04-2014-layers.s3.amazonaws.com/snapshots/(略)",
        "CodeSha256": "r6d7CEpYmwrgQIb3642rTsMEquxDjTQCE9MWnk0Qjfw=",
        "CodeSize": 8608150
    },
    "LayerArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:amb-boto3",
    "LayerVersionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:amb-boto3:1",
    "Description": "",
    "CreatedDate": "2019-07-01T04:49:38.540+0000",
    "Version": 1,
    "CompatibleRuntimes": [
        "python3.7"
    ]
}
```

#### 各パラメータ値を取得する

ブロックチェーンネットワークを構築するのに必要となるパラメータを取得します。

##### VPCとサブネット

インターフェイスVPCエンドポイントやセキュリティグループ作成を行う先のVPCは既存のものを利用する前提です。新規作成する場合はCFnテンプレートにリソース定義を追加するか手動で作成しておく必要があります。

```console
> aws ec2 describe-vpcs \
  --query "Vpcs"

[
    {
        "CidrBlock": "172.31.0.0/16",
        "DhcpOptionsId": "dopt-b06bd8c8",
        "State": "available",
        "VpcId": "vpc-xxxxxxxx",
        "OwnerId": "xxxxxxxxxxxx",
        "InstanceTenancy": "default",
        "CidrBlockAssociationSet": [
            {
                "AssociationId": "vpc-cidr-assoc-2b23e646",
                "CidrBlock": "172.31.0.0/16",
                "CidrBlockState": {
                    "State": "associated"
                }
            }
        ],
        "IsDefault": true
    },
    (略)
]
```

サブネットもインターフェイスVPCエンドポイントを作成するのに必要となります。
こちらも既存のものを利用する前提です。

```console
> aws ec2 describe-subnets \
  --query "Subnets"

[
    {
        "AvailabilityZone": "us-east-1a",
        "AvailabilityZoneId": "use1-az2",
        "AvailableIpAddressCount": 4088,
        "CidrBlock": "172.31.80.0/20",
        "DefaultForAz": true,
        "MapPublicIpOnLaunch": true,
        "State": "available",
        "SubnetId": "subnet-xxxxxxxx",
        "VpcId": "vpc-xxxxxxxx",
        "OwnerId": "xxxxxxxxxxxx",
        "AssignIpv6AddressOnCreation": false,
        "Ipv6CidrBlockAssociationSet": [],
        "SubnetArn": "arn:aws:ec2:us-east-1:xxxxxxxxxxxx:subnet/subnet-xxxxxxxx"
    },
]
```

##### 自身のグローバルIP

Hyperledger FabricのクライアントとなるEC2インスタンスへSSHでアクセスできるようにセキュリティグループを作成しています。そちらの`CidrIp` を指定するのに自身のグローバルIPを取得します。

```console
> curl ifconfig.io

xxx.xxx.xxx.xxx
```

### スタック作成

準備ができたらCFnでスタックを作成します。
AWS CLIの`aws cloudformation create-stack` コマンドで作成していますが、AWSマネジメントコンソールを利用してもOKです。スタック作成に必要となるパラメータは最低限を指定しています。詳細はGitHubから取得したCFnテンプレートをご参考ください。
リージョンはAMBがバージニア北部のみに対応しているので、`us-east-1` を指定します。

```console
> aws cloudformation create-stack \
  --stack-name amb-cfn-test \
  --template-body file://hyperledger-fabric-network.yaml \
  --capabilities CAPABILITY_IAM \
  --region us-east-1 \
  --parameters '[
    {
      "ParameterKey": "VpcId",
      "ParameterValue": "vpc-xxxxxxxx"
    },
    {
      "ParameterKey": "SubnetId",
      "ParameterValue": "subnet-xxxxxxxx"
    },
    {
      "ParameterKey": "NetworkName",
      "ParameterValue": "FabricNetwork"
    },
    {
      "ParameterKey": "NetworkDescription",
      "ParameterValue": "Hyperledger Fabric Network"
    },
    {
      "ParameterKey": "AdminUsername",
      "ParameterValue": "AdminUser"
    },
    {
      "ParameterKey": "AdminPassword",
      "ParameterValue": "Password123"
    },
    {
      "ParameterKey": "OrgName",
      "ParameterValue": "org1"
    },
    {
      "ParameterKey": "Boto3LambdaLayerArn",
      "ParameterValue": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:amb-boto3:1"
    },
    {
      "ParameterKey": "EC2KeyPairName",
      "ParameterValue": "amb-client-ec2-key"
    },
    {
      "ParameterKey": "ClientSSHCidrIp",
      "ParameterValue": "xxx.xxx.xxx.xxx/32"
    }
  ]'

{
    "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/amb-cfn-test/a1b77a10-9bdb-11e9-9649-0e8cdcc47538"
}
```

スタック作成完了までに30分くらいかかります。
作成完了したら`aws cloudformation describe-stacks` でスタック情報を取得して確認します。

```console
> aws cloudformation describe-stacks \
  --stack-name amb-cfn-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/amb-cfn-test/a1b77a10-9bdb-11e9-9649-0e8cdcc47538",
            "StackName": "amb-cfn-test",
            "Parameters": [
                {
                    "ParameterKey": "NetworkName",
                    "ParameterValue": "FabricNetwork"
                },
                {
                    "ParameterKey": "InstanceProfileName",
                    "ParameterValue": ""
                },
                {
                    "ParameterKey": "ClientSSHCidrIp",
                    "ParameterValue": "xxx.xxx.xxx.xxx/32"
                },
                {
                    "ParameterKey": "Boto3LambdaLayerArn",
                    "ParameterValue": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:amb-boto3:1"
                },
                {
                    "ParameterKey": "OrgName",
                    "ParameterValue": "org1"
                },
                {
                    "ParameterKey": "SubnetId",
                    "ParameterValue": "subnet-xxxxxxxx"
                },
                {
                    "ParameterKey": "AdminUsername",
                    "ParameterValue": "AdminUser"
                },
                {
                    "ParameterKey": "VpcId",
                    "ParameterValue": "vpc-xxxxxxxx"
                },
                {
                    "ParameterKey": "EC2KeyPairName",
                    "ParameterValue": "amb-client-ec2-key"
                },
                {
                    "ParameterKey": "AdminPassword",
                    "ParameterValue": "Password123"
                },
                {
                    "ParameterKey": "NetworkDescription",
                    "ParameterValue": "Hyperledger Fabric Network"
                }
            ],
            "CreationTime": "2019-07-01T08:38:42.505Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "MemberId",
                    "OutputValue": "m-XXXXXXXXXXXXXXXXXXXXXXXXXX"
                },
                {
                    "OutputKey": "ClientPublicIp",
                    "OutputValue": "xxx.xxx.xxx.xxx"
                },
                {
                    "OutputKey": "ClientInstanceId",
                    "OutputValue": "i-xxxxxxxxxxxxxxxxx"
                },
                {
                    "OutputKey": "PeerNodeId",
                    "OutputValue": "nd-XXXXXXXXXXXXXXXXXXXXXXXXXX"
                },
                {
                    "OutputKey": "NetworkId",
                    "OutputValue": "n-XXXXXXXXXXXXXXXXXXXXXXXXXX"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
```

### 動作確認

CFnでリソース作成できたらEC2インスタンスにログインしてブロックチェーンネットワーク構築とHyperledger Fabricのチャネル作成、チェーンコードがデプロイができたかを確認します。
スタック情報の`Outputs` にある`ClientPublicIp` がEC2インスタンスのグローバルIPアドレスになりますので、最初に作成したキーペアファイルとでEC2インスタンスにSSHでログインします。

```console
> ssh -i amb-client-ec2-key.pem ec2-user@xxx.xxx.xxx.xxx
```

EC2インスタンスのリソース作成で定義している`UserData` の実行ログは`/var/log/cloud-init-output.log` で確認できます。

```console:EC2インスタンス内
$ cat /var/log/cloud-init-output.log
(略)
+ docker exec cli peer channel join -b mychannel.block -o orderer.n-xxxxxxxxxxxxxxxxxxxxxxxxxx.managedblockchain.us-east-1.amazonaws.com:30001 --cafile /opt/home/managedblockchain-tls-chain.pem --tls
2019-07-01 09:17:03.663 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-01 09:17:03.903 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
+ sleep 30s
+ docker exec cli peer chaincode install -n mycc -v v0 -p github.com/chaincode_example02/go
2019-07-01 09:17:34.076 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-01 09:17:34.076 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-01 09:17:34.490 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
+ sleep 30s
+ docker exec cli peer chaincode instantiate -o orderer.n-xxxxxxxxxxxxxxxxxxxxxxxxxx.managedblockchain.us-east-1.amazonaws.com:30001 -C mychannel -n mycc -v v0 -c '{"Args":["init","a","100","b","200"]}' --cafile /opt/home/managedblockchain-tls-chain.pem --tls
2019-07-01 09:17:44.686 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-01 09:17:44.686 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
+ sleep 30s
+ docker exec cli peer chaincode list --instantiated -o orderer.n-xxxxxxxxxxxxxxxxxxxxxxxxxx.managedblockchain.us-east-1.amazonaws.com:30001 -C mychannel --cafile /opt/home/managedblockchain-tls-chain.pem --tls
Get instantiated chaincodes on channel mychannel:
Name: mycc, Version: v0, Path: github.com/chaincode_example02/go, Escc: escc, Vscc: vscc
+ /opt/aws/bin/cfn-signal -e 0 --stack amb-cfn-test --resource BlockchainClient --region us-east-1
Cloud-init v. 0.7.6 finished at Mon, 01 Jul 2019 09:19:16 +0000. Datasource DataSourceEc2.  Up 692.70 seconds
```

`docker exec cli peer chaincode list --instantiated` コマンドでチェーンコードがデプロイされていることが確認できます。

EC2インスタンス内で`cli peer chaincode query` コマンドが実行できるか確認します。

```console:EC2インスタンス内
$ docker exec \
  cli peer chaincode query \
  -C mychannel \
  -n mycc -c '{"Args":["query","a"]}'

100
```

やったぜ。


### スタックの削除

ブロックチェーンネットワークが不要になったらCFnのスタック削除をすることで作成したリソースが削除できます。
Amazon CloudWatchに出力されるログや手動で作成したキーペア、Lmabda Layerは削除されないので適宜手動削除ください。

```console
> aws cloudformation delete-stack \
  --stack-name amb-cfn-test
```


## まとめ

AMBを利用すると比較的容易にブロックチェーンネットワークが構築できますが、Hyperledger Fabricのチャネル作成やチェーンコードデプロイまでを含めると構築に手間がかかっていたので、CFnテンプレートを作成する意義はあったかなと思います。

ただ、CFnではまだAMBのリソースが対応しておらず、Lambda-Backedカスタムリソースを多用する必要があったので、テンプレート作成するにも思った以上にハマったり手間がかかりました。(白目

CFnのcfn-initヘルパースクリプトを利用すればEC2インスタンスで実行するコマンドなどもメタデータとしてテンプレートで管理できそうでしたが、おそらく相当にハマりそうだったので今回は踏み込みませんでした。

テンプレート作成する際のポイントは別記事にしてまとめようと思います。


## 参考

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

AWS CloudFormationのAWS Lambda-backedカスタムリソースで最新のAWS SDKを利用する - Qiita
https://qiita.com/kai_kou/items/db0924f8bbd0fd03eb94