cloudpack あら便利カレンダー 2019 の記事となります。誕生秘話 はこちら。

Amazon Managed Blockchain(AMB)でブロックチェーンネットワークを構築するのが手間になったので、AWS CloudFormation(CFn)を利用して構築できるようにしてみました。使い方は下記をご参考ください。

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

Cfnのテンプレートを作成するのにいくつかハマったりしたので解説がてらまとめてみます。
テンプレートはGitHubにアップしています。

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

CFnがAMBリソースに対応していない

AMBのネットワークやメンバーなどのリソースがCFnでサポートされていません。(2019/07/03時点)

AWS Resource and Property Types Reference – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

Release History – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ReleaseHistory.html

そのためAWS Lambda-backedカスタムリソースを利用してLambda関数でAWS SDKを利用してAMBのネットワークやメンバーを作成する必要があります。

詳細は下記が参考になります。

AWS SDK for Python(boto3)でAmazon Managed Blockchainのブロックチェーンネットワークを作成してみた – Qiita
https://cloudpack.media/47241

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 – Qiita
https://cloudpack.media/48205

リソースの作成順を制御する

AMBのPeerノードはネットワーク(メンバー)が利用可能になってから追加する必要があるため、CFnのDependsOn 属性でリソースの作成順を制御してネットワーク(→メンバー)→Peerノードの順にリソースを作成します。
※ネットワークで最初のメンバーはネットワークと同時に作成する必要があります。

DependsOn 属性 – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html

AWS Lambda-backedカスタムリソースでリソース作成完了を待ち受ける

DependsOn 属性でリソースの作成順を制御することができたのですが、AMBのリソース(ネットワーク、Peerノード)をAWS CLIやAWS SDKで作成するとNetworkIdなどがすぐにレスポンスとして返ってきます。AMBだと「レスポンスが返ってきた=リソース作成できた」ではなく「レスポンスが返ってきた=リソース作成が開始された」となります。リソースが作成完了し利用可能になるには20分ほどかかります。

なので、CFnでネットワーク作成してすぐにPeerノードを作成しようとするとネットワークが利用可能になっておらずエラーになります。

そのためCFnでリソースの作成を待ち受けるのにAWS Lambda-backedカスタムリソースでどうにかする必要がありますが、AMBネットワークの作成には20分程度かかり、AWS Lmabdaのタイムアウト(15分)を超えてしまうため、作成用のカスタムリソースだけでは待受けすることができません。
そこで、待受用のカスタムリソースを定義することで、AWS Lambdaのタイムタウト(15分) x リトライ回数(3回)で45分まで待ち受けられるようにします。詳細は下記が参考になります。

AWS CloudFormationのLambda-Backedカスタムリソースでリソース作成を待ち受けできるようにする – Qiita
https://cloudpack.media/48222

このリソース作成を待ち受ける実装とDependsOn属性を利用することでAMBのリソースが作成できるようになりました。下記はCFnのテンプレートから抜粋したリソース定義となります。

テンプレート抜粋

Resources:
  # ネットワーク作成用のカスタムリソース
  CreateBlockchainNetwork:
    Type: Custom::CustomResource

  # リソース作成待受用のカスタムリソース
  BlockchainNetwork:
    Type: Custom::CustomResource
    DependsOn: CreateBlockchainNetwork

  # リソース情報取得用のカスタムリソース
  BlockchainMember:
    Type: Custom::CustomResource
    DependsOn: BlockchainNetwork

  # Peerノード作成用のカスタムリソース
  CreateBlockchainPeerNode:
    Type: Custom::CustomResource
    DependsOn: BlockchainMember

  # リソース作成待受用のカスタムリソース
  BlockchainPeerNode:
    Type: Custom::CustomResource
    DependsOn: CreateBlockchainPeerNode

AWS Lambdaで利用できるAWS SDKを最新にする

AMBは2019/05/01にGAとなったサービスです。

New – Amazon Managed Blockchain – Create & Manage Scalable Blockchain Networks | AWS News Blog
https://aws.amazon.com/jp/blogs/aws/new-amazon-managed-blockchain-create-manage-scalable-blockchain-networks

AWS Lambdaの関数(Python)で利用できるAWS SDK(boto3 1.9.42)だとAMBが対応していないバージョンとなるため、AWS Lambda Layersを利用して最新のAWS SDK(boto3 1.9.139 以上)を利用する必要があります。(2019/07/03時点)

boto3/CHANGELOG.rst at develop · boto/boto3
https://github.com/boto/boto3/blob/develop/CHANGELOG.rst#19139

api-change:managedblockchain: [botocore] Update managedblockchain client to latest version

エラー例

Lambda関数の実装

import json
import boto3

def lambda_handler(event, context):
    print(boto3.__version__)
    client = boto3.client("managedblockchain")

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

実行ログ

START RequestId: 1f574950-f61d-4d05-a2b6-a56e11eb2201 Version: $LATEST
1.9.42
[ERROR] UnknownServiceError: Unknown service: 'managedblockchain'. Valid service names are: acm, acm-pca, alexaforbusiness, apigateway, application-autoscaling, appstream, appsync, athena, autoscaling, autoscaling-plans, batch, budgets, ce, chime, cloud9, clouddirectory, cloudformation, cloudfront, cloudhsm, cloudhsmv2, cloudsearch, cloudsearchdomain, cloudtrail, cloudwatch, codebuild, codecommit, codedeploy, codepipeline, codestar, cognito-identity, cognito-idp, cognito-sync, comprehend, config, connect, cur, datapipeline, dax, devicefarm, directconnect, discovery, dlm, dms, ds, dynamodb, dynamodbstreams, ec2, ecr, ecs, efs, eks, elasticache, elasticbeanstalk, elastictranscoder, elb, elbv2, emr, es, events, firehose, fms, gamelift, glacier, glue, greengrass, guardduty, health, iam, importexport, inspector, iot, iot-data, iot-jobs-data, iot1click-devices, iot1click-projects, iotanalytics, kinesis, kinesis-video-archived-media, kinesis-video-media, kinesisanalytics, kinesisvideo, kms, lambda, lex-models, lex-runtime, lightsail, logs, machinelearning, macie, marketplace-entitlement, marketplacecommerceanalytics, mediaconvert, medialive, mediapackage, mediastore, mediastore-data, mediatailor, meteringmarketplace, mgh, mobile, mq, mturk, neptune, opsworks, opsworkscm, organizations, pi, pinpoint, pinpoint-email, polly, pricing, rds, redshift, rekognition, resource-groups, resourcegroupstaggingapi, route53, route53domains, s3, sagemaker, sagemaker-runtime, sdb, secretsmanager, serverlessrepo, servicecatalog, servicediscovery, ses, shield, signer, sms, snowball, sns, sqs, ssm, stepfunctions, storagegateway, sts, support, swf, transcribe, translate, waf, waf-regional, workdocs, workmail, workspaces, xray
(略)

AWS Lambda Layersを利用して最新のAWS SDKを利用する方法は下記が参考になります。

AWS CloudFormationのAWS Lambda-backedカスタムリソースで最新のAWS SDKを利用する – Qiita
https://cloudpack.media/48058

AMBリソースの更新・削除に対応する

AMBリソースはAWS Lambda-backedカスタムリソースで作成しているので、更新や削除もLambda-backedカスタムリソースで行う必要があります。

詳細は下記が参考になりますが、こちらもリソース作成の待受と同じく、作成とは別のカスタムリソースを定義する必要があります。

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 – Qiita
https://cloudpack.media/48205

作成待ちと更新・削除を担うカスタムリソースは共通化できたので、最終的には下記のようなリソース定義となりました。

テンプレート抜粋

Resources:
  # ネットワーク作成用のカスタムリソース
  CreateBlockchainNetwork:
    Type: Custom::CustomResource

  # リソース作成待受とリソース情報取得・更新・削除用のカスタムリソース
  BlockchainNetwork:
    Type: Custom::CustomResource
    Properties:
      NetworkId: !GetAtt CreateBlockchainNetwork.NetworkId
    DependsOn: CreateBlockchainNetwork

  # リソース情報取得用のカスタムリソース
  BlockchainMember:
    Type: Custom::CustomResource
    Properties:
      NetworkId: !GetAtt BlockchainNetwork.Network.Id
      MemberId: !GetAtt CreateBlockchainNetwork.MemberId
    DependsOn: BlockchainNetwork

  # Peerノード作成用のカスタムリソース
  CreateBlockchainPeerNode:
    Type: Custom::CustomResource
    Properties:
      NetworkId: !GetAtt BlockchainNetwork.Network.Id
      MemberId: !GetAtt BlockchainMember.Member.Id
    DependsOn: BlockchainMember

  # リソース作成待受とリソース情報取得・更新・削除用のカスタムリソース
  BlockchainPeerNode:
    Type: Custom::CustomResource
    Properties:
      NetworkId: !GetAtt BlockchainNetwork.Network.Id
      MemberId: !GetAtt BlockchainMember.Member.Id
      NodeId: !GetAtt CreateBlockchainPeerNode.NodeId
    DependsOn: CreateBlockchainPeerNode

AWS lambda-backedカスタムリソースで返すリソース情報(JSON)に気をつける

AWS lambda-backedカスタムリソースのLambda関数ではcfnresponse.send(event, context, cfnresponse.SUCCESS, data)のようにして処理結果をCFnに返すことができます。

最後のパラメータdataはJSONとなり、CFnのFn::GetAttで参照可能です。

カスタムリソースの応答オブジェクト – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html

応答で送信される、custom resource providerによって定義された名前と値のペア。ここで指定する値には、Fn::GetAtt を使用して、テンプレート内の名前でアクセスできます。

ただし、ネストされたJSONをdataに含めてもNetwork.Idのようにして参照できないため、AWS SDKでAMBのリソース情報を取得して得られるJSONをそのままでは返すことができませんでした。

AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない – Qiita
https://cloudpack.media/48318

なので、AWS SDKでAMBのリソース情報を取得して得られるJSONを加工する必要があります。
JSONのキーをNetwork.Idとすると、CFn側でもNetwork.Idと参照できるようにしています。

LambdaからCFnにネットワーク情報を返す例

import cfnresponse
import boto3
import json
from datetime import date, datetime
def handler(event, context):
  client = boto3.client("managedblockchain")
  networkId = event['ResourceProperties']['NetworkId']
  response = {}
  if event['RequestType'] == 'Create':
    network = client.get_network(
      NetworkId=networkId
    )

    orderingServiceEndpoint = network['Network']['FrameworkAttributes']['Fabric']['OrderingServiceEndpoint']
    vpcEndpointServiceName =  network['Network']['VpcEndpointServiceName']
    response = {
      "Network.Id": networkId,
      "Network.FrameworkAttributes.Fabric.OrderingServiceEndpoint": orderingServiceEndpoint,
      "Network.VpcEndpointServiceName": vpcEndpointServiceName
    }
  cfnresponse.send(event, context, cfnresponse.SUCCESS, response)

詳細は下記が参考になります。

AWS CloudFormationのLambda-BackedカスタムリソースでネストされてるっぽいJSONを返す方法 – Qiita
https://cloudpack.media/48329

Hyperledger FabricのクライアントをEC2インスタンスで構築する

Hyperledger FabricのクライアントをEC2インスタンスで構築する定義は下記を参考にさせてもらいました。

awslabs/amazon-managed-blockchain-client-templates: AWS CloudFormation templates to provision Amazon EC2 instances and install and configure clients for use with blockchain frameworks in Amazon Managed Blockchain
https://github.com/awslabs/amazon-managed-blockchain-client-templates

こちらはHyperledger FabricのクライアントとなるEC2インスタンスを作成するテンプレートでしたので、自前のテンプレートへ組み込み、AMBで必要となるリソースの作成後、インスタンスが作成されるようにしました。

ポイントとしては以下となります。

CFnのcfn-signalヘルパースクリプトで完了シグナルをCFnに返す

CFnでEC2インスタンスを作成すると、EC2インスタンスのステータスがRunningとなった時点でリソース作成完了となります。
そのため、テンプレートのUserDataで定義しているコマンド実行でエラーとなっても正常完了扱いとなり不便だったので、CFnのcfn-signalヘルパースクリプトで完了シグナルをCFnに返すようにしました。

cfn-signalを利用するには、CreationPolicyを定義する必要があります。

EC2インスタンス定義_一部抜粋

 BlockchainClient:
    Type: AWS::EC2::Instance
      UserData:
        Fn::Base64:
          Fn::Sub:
            - |
              #!/bin/bash
              (略)
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource BlockchainClient --region ${AWS::Region}
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M

詳細は下記が参考になります。

cfn-signal – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

Fn::Subで変数の取扱い

UserDataに指定する値はFn::Base64Fn::Subを用いて指定しています。
Fn::Subは文字列中に置き換えたい変数がある場合に利用する関数となっています。

Fn::Sub – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html

テンプレートで、スタックを作成または更新するまで使用できない値を含むコマンドまたは出力を作成するために、この関数を使用できます。

UserDataを編集しているとシェル変数を利用したくなるケースがでてきますが、そのまま利用するとエラーとなるため注意が必要です。シェル変数の利用方法は下記が参考になりました。

CloudFormationの中のEC2のユーザーデータでシェル変数を使用する | DevelopersIO
https://dev.classmethod.jp/cloud/aws/using-variables-in-ec2-user-data-in-cloudformation/

UserDataで変数を利用する例

 UserData:
        Fn::Base64:
          Fn::Sub:
            - |
              #!/bin/bash

              # これはOK
              echo ${HOGE}

              HOGE2=hogehoge

              # これはエラーになる
              echo ${HOGE2}

              # こうするとOK
              echo ${!HOGE2}
            - {
              HOGE: hoge
            }

Hyperledger Fabricのcliでコマンドを実行するタイミングに気をつける

以下は、UserDataの後半部分の抜粋となります。ところどころでsleepコマンドを実行して待受けています。
リソース作成を繰り返し試行錯誤した結果となりますが、主に下記の理由からとなります。

  • Peerノードが利用可能になるのを待ち受け
  • OrdererからPeerノードへのデータ送信待ち

UserData一部抜粋

(略)
/usr/local/bin/docker-compose -f docker-compose-cli.yaml up -d
sleep 5m

# enroll
fabric-ca-client enroll -u https://${ADMIN_USERNAME}:${ADMIN_PASSWORD}@${FABRIC_CA_ENDPOINT} --tls.certfiles /home/ec2-user/${FABRIC_CA_FILE} -M /home/ec2-user/admin-msp
cp -r /home/ec2-user/admin-msp/signcerts /home/ec2-user/admin-msp/admincerts
echo '
Organizations:
(略)
' > /home/ec2-user/configtx.yaml
docker exec cli configtxgen -outputCreateChannelTx /opt/home/mychannel.pb -profile OneOrgChannel -channelID mychannel --configPath /opt/home/
sleep 30s

# Create Channel
docker exec cli peer channel create -c mychannel -f /opt/home/mychannel.pb -o ${ORDERING_SERVICE_ENDPOINT} --cafile /opt/home/${FABRIC_CA_FILE} --tls
sleep 30s

docker exec cli peer channel join -b mychannel.block -o ${ORDERING_SERVICE_ENDPOINT} --cafile /opt/home/${FABRIC_CA_FILE} --tls
sleep 30s

# Install ChainCode
docker exec cli peer chaincode install -n mycc -v v0 -p github.com/chaincode_example02/go
docker exec cli peer chaincode instantiate -o ${ORDERING_SERVICE_ENDPOINT} -C mychannel -n mycc -v v0 -c '{"Args":["init","a","100","b","200"]}' --cafile /opt/home/${FABRIC_CA_FILE} --tls
sleep 30s

docker exec cli peer chaincode list --instantiated -o ${ORDERING_SERVICE_ENDPOINT} -C mychannel --cafile /opt/home/${FABRIC_CA_FILE} --tls

/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource BlockchainClient --region ${AWS::Region}

UserData の実行ログの確認方法

UserDataで指定したコマンドの実行ログが確認できないか調べてみたらしっかりと出力されていました。
下記が参考になりました。

Linux インスタンスでの起動時のコマンドの実行 – Amazon Elastic Compute Cloud
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

AWSのCloud initのログの場所 | しびら
http://yamada.daiji.ro/blog/?p=191

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

まとめ

AMBのリソースをCFnで管理するのにAWS Lambda-backedカスタムリソースを利用することができましたが、そこそこハマるところがあり、テンプレート作成に時間がかかりました。
1度作成できたら応用を効かせることができそうですので、個人的には良いテンプレートができたなと思ってます^^

参考

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

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

AWS Resource and Property Types Reference – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

Release History – AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ReleaseHistory.html

AWS SDK for Python(boto3)でAmazon Managed Blockchainのブロックチェーンネットワークを作成してみた – Qiita
https://cloudpack.media/47241

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 – Qiita
https://cloudpack.media/48205

DependsOn 属性 – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html

AWS CloudFormationのLambda-Backedカスタムリソースでリソース作成を待ち受けできるようにする – Qiita
https://cloudpack.media/48222

New – Amazon Managed Blockchain – Create & Manage Scalable Blockchain Networks | AWS News Blog
https://aws.amazon.com/jp/blogs/aws/new-amazon-managed-blockchain-create-manage-scalable-blockchain-networks

boto3/CHANGELOG.rst at develop · boto/boto3
https://github.com/boto/boto3/blob/develop/CHANGELOG.rst#19139

AWS CloudFormationのAWS Lambda-backedカスタムリソースで最新のAWS SDKを利用する – Qiita
https://cloudpack.media/48058

カスタムリソースの応答オブジェクト – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html

AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない – Qiita
https://cloudpack.media/48318

AWS CloudFormationのLambda-BackedカスタムリソースでネストされてるっぽいJSONを返す方法 – Qiita
https://cloudpack.media/48329

awslabs/amazon-managed-blockchain-client-templates: AWS CloudFormation templates to provision Amazon EC2 instances and install and configure clients for use with blockchain frameworks in Amazon Managed Blockchain
https://github.com/awslabs/amazon-managed-blockchain-client-templates

cfn-signal – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

Fn::Sub – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html

CloudFormationの中のEC2のユーザーデータでシェル変数を使用する | DevelopersIO
https://dev.classmethod.jp/cloud/aws/using-variables-in-ec2-user-data-in-cloudformation/

Linux インスタンスでの起動時のコマンドの実行 – Amazon Elastic Compute Cloud
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

AWSのCloud initのログの場所 | しびら
http://yamada.daiji.ro/blog/?p=191

元記事はこちら

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