Amazon Managed BlockchainをCloudFormationで作成する

2022.01.10

いわさです。

ブロックチェーンを触っているとスマートコントラクトを気軽に検証したいシーンが多いと思いますが、パブリックブロックチェーンではメインネットだとガスが必要になり、プライベートブロックチェーンでもネットワークやノードのランニングコストが必要になります。
これは、Amazon Managed Blockchainにおいても同様です。

そうなると検証用途としては、パブリックの場合はテストネットなどを利用する、プライベートの場合はリソースを作成したり削除したりを繰り返すことになりますが、非クラウド環境より調達が楽になったとはいえそれでもリソース作成の手順が多かったり待ち時間を何度も挟むことになります。

CloudFormationを使って自動化

Amazon Managed BlockchainはCloudFormationでサポートされているのでテンプレートを用意してみました。

先日、Amazon Managed Blockchain(AMB)でHyperledger Fabricのv2.2を試しましたが、検証環境をすぐ用意できるようにしておきたいと思ったので、AWS側のリソース作成を全てテンプレート化しました。(厳密には、クライアントリソースは今回含めていません)

作成するリソース

作成したテンプレートはこちらへ置いておきます。

このテンプレートでは、フレームワークはHyperledger Fabric v2.2で構成しています。
スタックを作成すると以下のリソースが作成されます。

スタック作成後は以下のネットワーク上にhyperledger/fabric-toolsクライアントなどをデプロイして操作をして頂くイメージです。
ネットワークはパブリックサブネットに配置しているので、適宜テンプレートを調整してください。

リソースの説明

本日時点でAmazon Managed BlockchainのCloudFormationテンプレートの記事はあまり多くないので、この記事では少し構成について触れたいと思います。

現時点でCloudFormationでサポートされているリソースは以下の2つです。

Managed Blockchainを触ったことがある方は、メンバーとノードだけ?と一瞬思うかもしれません。

ネットワーク&メンバー

メンバーリソースではネットワーク構成を指定することが出来ます。
また、ネットワーク構成を指定せずに既存ネットワークIDを指定することで追加メンバーとしてリソースを作成することも可能です。

言われて見ると、Managed Blockchainのネットワーク削除は最後のメンバーを削除することで暗黙的に実行されるので納得出来ます。

NetworkAndInitialMember:
  Type: "AWS::ManagedBlockchain::Member"
  Properties:
    NetworkConfiguration:
      Name: !Ref NetworkName
      Framework: "HYPERLEDGER_FABRIC"
      FrameworkVersion: "2.2"
      NetworkFrameworkConfiguration:
        NetworkFabricConfiguration:
          Edition: !Ref Edition
      VotingPolicy:
        ApprovalThresholdPolicy:
          ThresholdPercentage: !Ref ThresholdPercentage
          ProposalDurationInHours: !Ref ProposalDurationInHours
          ThresholdComparator: !Ref ThresholdComparator
    MemberConfiguration:
      Name: !Ref MemberName
      MemberFrameworkConfiguration:
        MemberFabricConfiguration:
          AdminUsername: !Ref MemberAdminUsername
          AdminPassword: !Ref MemberAdminPassword

ノード

ノードについては、インスタンスタイプとAvailability Zoneを指定するだけです。

InitialNode:
  Type: "AWS::ManagedBlockchain::Node"
  Properties:
    NetworkId: !GetAtt NetworkAndInitialMember.NetworkId
    MemberId: !GetAtt NetworkAndInitialMember.MemberId
    NodeConfiguration:
      InstanceType: !Ref InstanceType
      AvailabilityZone: !Select [ 0, "Fn::GetAZs": {Ref: "AWS::Region"}]

VPCエンドポイント

Managed Blockchainへプライベートアクセスする際には、VPCエンドポイントを利用する必要があります。
リソースから取得できるネットワークIDが大文字で、VPCエンドポイント作成時にエラーになってしまうので、ここではカスタムリソースで作成しました。
AMBのAPIからエンドポイント名を取得しています。

ManagedBlockchainVpcEndpoint:
  Type: AWS::CloudFormation::CustomResource
  Properties: 
    ServiceToken: !GetAtt ManagedBlockchainVpcEndpointLambda.Arn
    NetworkId: !GetAtt NetworkAndInitialMember.NetworkId
    VpcId: !Ref VPC
    SubnetIds:
      - !Ref Subnet1
    SecurityGroupIds:
      - !Ref FabricSecurityGroup

ManagedBlockchainVpcEndpointLambda:
  Type: AWS::Lambda::Function
  Properties:
    Code:
      ZipFile: |
        import json
        import boto3
        import cfnresponse
        def handler(event, context):
          try:
            print(event)
            params = dict([(k, v) for k, v in event['ResourceProperties'].items() if k != 'ServiceToken'])
            amb = boto3.client('managedblockchain')
            response = amb.get_network(
              NetworkId=event['ResourceProperties']['NetworkId']
            )
            vpc_endpoint_service_name = response['Network']['VpcEndpointServiceName']
            client = boto3.client('ec2')
            if event['RequestType'] == 'Create':
              response = client.create_vpc_endpoint(
                VpcEndpointType='Interface',
                VpcId=event['ResourceProperties']['VpcId'],
                ServiceName=vpc_endpoint_service_name,
                SubnetIds=event['ResourceProperties']['SubnetIds'],
                SecurityGroupIds=event['ResourceProperties']['SecurityGroupIds'],
                PrivateDnsEnabled=True
              )
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, 'ManagedBlockchainVpcEndpointLambda')
            if event['RequestType'] == 'Delete':
              response = client.describe_vpc_endpoints(
                Filters=[
                  {
                    'Name': 'service-name',
                    'Values': [ vpc_endpoint_service_name ]
                  },
                ],
              )
              response = client.delete_vpc_endpoints(
                VpcEndpointIds=[
                    response['VpcEndpoints'][0]['VpcEndpointId'],
                ]
              )
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, 'ManagedBlockchainVpcEndpointLambda')
            if event['RequestType'] == 'Update':
              cfnresponse.send(event, context, cfnresponse.FAILED, {}, 'ManagedBlockchainVpcEndpointLambda')
          except Exception as e:
            cfnresponse.send(event, context, cfnresponse.FAILED, {}, 'ManagedBlockchainVpcEndpointLambda')
    Handler: index.handler
    MemorySize: 128
    Role: !GetAtt LambdaExecuteRole.Arn
    Runtime: python3.9
    Timeout: 60

このカスタムリソースは作成と削除は対応していますが、更新は考慮できていないのでご注意ください。

さいごに

本日はAmazon Managed BlockchainのCloudFormationテンプレートサンプルを紹介しました。

実際にはこの上にのるクライアントを使って証明書やチャネル、チェーンコードの準備がさらに大変なので、そのあたりも自動化できるようにしたいと思います。