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)を利用して構築できるようにしてみました。使い方は下記をご参考ください。

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


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://qiita.com/kai_kou/items/9e16274b6a5c30aa6086

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 - Qiita
https://qiita.com/kai_kou/items/7be2eb9a36611bb5da12


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

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://qiita.com/kai_kou/items/57699a4e84ded2c1bfc4


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

```yaml:テンプレート抜粋
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


### エラー例

```py: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!')
    }
```

```txt:実行ログ
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://qiita.com/kai_kou/items/db0924f8bbd0fd03eb94


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

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

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

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 - Qiita
https://qiita.com/kai_kou/items/7be2eb9a36611bb5da12


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

```yaml:テンプレート抜粋
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://qiita.com/kai_kou/items/61a2b3c69ae2af4f2e40


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

```py: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://qiita.com/kai_kou/items/d030117c694531410203


## 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` を定義する必要があります。

```yaml: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::Base64` と`Fn::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/


```yaml: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ノードへのデータ送信待ち


```console: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


```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
```


## まとめ

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


## 参考

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

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://qiita.com/kai_kou/items/9e16274b6a5c30aa6086

AWS CloudFormationのLambda-backedカスタムリソースでリソースの更新・削除をする方法 - Qiita
https://qiita.com/kai_kou/items/7be2eb9a36611bb5da12

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

AWS CloudFormationのLambda-Backedカスタムリソースでリソース作成を待ち受けできるようにする - Qiita
https://qiita.com/kai_kou/items/57699a4e84ded2c1bfc4

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://qiita.com/kai_kou/items/db0924f8bbd0fd03eb94

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

AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない - Qiita
https://qiita.com/kai_kou/items/61a2b3c69ae2af4f2e40

AWS CloudFormationのLambda-BackedカスタムリソースでネストされてるっぽいJSONを返す方法 - Qiita
https://qiita.com/kai_kou/items/d030117c694531410203

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