CloudFormationでリソースを作成しようとしたときに「Fn::Sub syntax must contain only alphanumeric characters, underscores, periods, and colons」というエラーが出たのでその時の対処方法をブログに残します。
使用したCloudFormationテンプレート
以下のCloudFormationテンプレートはEC2を作成するものです。
さらにEC2の作成時にシェルスクリプトを実行するようにUserDataを設定してます。
UserDataではLinuxのユーザーを作成してEC2のメタデータからパブリックIPアドレスを取得して文字数をカウントしています。
AWSTemplateFormatVersion: "2010-09-09"
Description: ec2 Stack
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
VolumeSize:
Default: 8
Type: Number
Ec2InstanceType:
Default: t2.micro
Type: String
Vpcid:
Type: AWS::EC2::VPC::Id
Description: Enter VPC ID
PublicSubnet1:
Type: AWS::EC2::Subnet::Id
Description: Enter Subnet ID
LinuxUser:
Default: kobayashi
Type: String
Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
Ec2SsmRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RoleName: EC2SsmRole
Ec2IamInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: Ec2InstanceProfile
Roles:
- !Ref Ec2SsmRole
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
Ec2Sg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for EC2
GroupName: ec2-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
Tags:
- Key: Name
Value: ec2-sg
VpcId: !Ref Vpcid
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
Ec2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: 3000
VolumeSize: !Ref VolumeSize
VolumeType: gp3
IamInstanceProfile: !Ref Ec2IamInstanceProfile
ImageId: ami-03dceaabddff8067e
InstanceType: !Ref Ec2InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref Ec2Sg
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: ec2
UserData:
Fn::Base64:
!Sub
|-
#!/bin/bash
useradd ${LinuxUser}
if [ -d /home/${LinuxUser} ]; then
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` && curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4 > /home/${LinuxUser}/meta_data.txt
public_ip=`cat /home/${LinuxUser}/meta_data.txt`
fi
echo ${#public_ip} > /home/${LinuxUser}/word_count
このCloudFormationテンプレートを以下のAWS CLIコマンドでデプロイしてみます。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Vpcid,ParameterValue=VPCのID ParameterKey=PublicSubnet1,ParameterValue=パブリックサブネットのID --capabilities CAPABILITY_NAMED_IAM
コマンドを実行すると以下のエラーが発生します。
An error occurred (ValidationError) when calling the CreateStack operation: Template error: variable names in Fn::Sub syntax must contain only alphanumeric characters, underscores, periods, and colons
原因と解決方法
原因はUserDataで設定したシェルスクリプト内にある「${#public_ip}」です。
UserDataではLinuxユーザーの名前をCloudFormationのパラメーターから取ってくるようにするため「Fn::Sub」を使用しています。
そのため、「${#public_ip}」もCloudFormationの変数として認識されてしまっています。
エラーの内容の通り、英数字、アンダースコアしか使用できないためエラーになっています。
解決方法は「Fn::Sub」のドキュメントに記載されていました。
USD 記号と中括弧 (${}) をそのまま書き込むには、最初の中括弧の後に感嘆符 (!) を追加します (${!Literal} など)。CloudFormation は、このテキストを ${Literal} として解決します。
つまり、感嘆符「!」を使用してエスケープする必要があります。
なのでUserDataの「${#public_ip}」を「${!#public_ip}」にすることで解決します。
エラーを解決したCloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: ec2 Stack
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
VolumeSize:
Default: 8
Type: Number
Ec2InstanceType:
Default: t2.micro
Type: String
Vpcid:
Type: AWS::EC2::VPC::Id
Description: Enter VPC ID
PublicSubnet1:
Type: AWS::EC2::Subnet::Id
Description: Enter Subnet ID
LinuxUser:
Default: kobayashi
Type: String
Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
Ec2SsmRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RoleName: EC2SsmRole
Ec2IamInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: Ec2InstanceProfile
Roles:
- !Ref Ec2SsmRole
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
Ec2Sg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for EC2
GroupName: ec2-sg
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
Tags:
- Key: Name
Value: ec2-sg
VpcId: !Ref Vpcid
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
Ec2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: 3000
VolumeSize: !Ref VolumeSize
VolumeType: gp3
IamInstanceProfile: !Ref Ec2IamInstanceProfile
ImageId: ami-03dceaabddff8067e
InstanceType: !Ref Ec2InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref Ec2Sg
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: ec2
UserData:
Fn::Base64:
!Sub
|-
#!/bin/bash
useradd ${LinuxUser}
if [ -d /home/${LinuxUser} ]; then
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` && curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4 > /home/${LinuxUser}/meta_data.txt
public_ip=`cat /home/${LinuxUser}/meta_data.txt`
fi
echo ${!#public_ip} > /home/${LinuxUser}/word_count
このCloudFormationテンプレートを以下のAWS CLIコマンドでデプロイするとエラーが出なくなっています。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Vpcid,ParameterValue=VPCのID ParameterKey=PublicSubnet1,ParameterValue=パブリックサブネットのID --capabilities CAPABILITY_NAMED_IAM
さいごに
普段使用していてもまだまだ知らない仕様があるなといった感想です。
CloudFormationの関数は便利なものが多いのでしっかり仕様を把握して使いこなせるように今後も学習を続けていきます。