CloudFormationで「Fn::Sub syntax must contain only alphanumeric characters, underscores, periods, and colons」と出た時の対処方法

2023.05.28

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の関数は便利なものが多いのでしっかり仕様を把握して使いこなせるように今後も学習を続けていきます。