この記事では、CloudFormationのエラー対処の過程で学んだことを共有したいと思います。
EC2インスタンスを作成したVPCサブネットに配置し、パブリックIPの自動割り当てを有効化する場合の記法
例えば、VPCとセキュリティグループを作成し、作成したVPCサブネット内にEC2インスタンスを割り当てたいとします。
また、パブリックIPを自動的に割り当てるためにNetworkInterfacesのプロパティであるAssociatePublicIpAddressを有効化します。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
KeyName:
Type: String
Description: Key pair of the instance
MyIpParameter:
Type: String
Description: Your current public IP address
NoEcho: true
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.16.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value: sample-vpc
# Subnet
Subnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 172.16.0.0/24
# ap-northeast-1aに配置
AvailabilityZone: !Select
- '0'
- !GetAZs ''
Tags:
- Key: Name
Value: sample-subnet-1a
# SecurityGroup
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access via port 22, and allow all incoming HTTP (port 80) connections
GroupName: WebServer-SG
SecurityGroupIngress:
- CidrIp: !Join ['', [!Ref MyIpParameter, '/32']]
IpProtocol: tcp
FromPort: 22
ToPort: 22
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 80
ToPort: 80
VpcId: !Ref VPC
Tags:
- Key: Name
Value: WebServer-SG
# Web server
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-08a8688fb7eacb171
InstanceType: t2.micro
KeyName: !Ref KeyName
SubnetId: !Ref Subnet1a
SecurityGroupIds:
- !Ref EC2SecurityGroup
NetworkInterfaces:
- DeviceIndex: 0
AssociatePublicIpAddress: true
Tags:
- Key: Name
Value: SampleWebServer
しかし、このテンプレートを使ってスタックを作成しようとすると、以下のようなエラーが発生し、ROLL_BACKとなってしまいます。
このエラーメッセージは、ネットワークインターフェイスのサブネットとインスタンスレベルのサブネットを同時に指定しようとすると発生します。
原因となっているのは、以下の記述です。
WebServer:
Type: AWS::EC2::Instance
Properties:
# ...
SubnetId: !Ref Subnet1a
SecurityGroupIds:
- !Ref EC2SecurityGroup
NetworkInterfaces:
- DeviceIndex: 0
AssociatePublicIpAddress: true
cfn-lintでチェックすると、インスタンス直下のプロパティのSubnetIdはネットワークインターフェイスと同時に存在することはできないとのことです。
以下のように、サブネットとセキュリティグループをネットワークインターフェイス内のプロパティとして指定するとエラーは解消します。
# 他は全て同じ
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-08a8688fb7eacb171
InstanceType: t2.micro
KeyName: !Ref KeyName
NetworkInterfaces:
- DeviceIndex: 0
AssociatePublicIpAddress: true
SubnetId: !Ref Subnet1a
GroupSet:
- !Ref EC2SecurityGroup
意図した通り、パブリックIPが割り当てられると同時に、インスタンスがテンプレート内で作成したVPCサブネット内に配置されていることがわかります。
これは作成したVPCを指定すると発生するエラーです。
VPCを指定せずに、デフォルトVPCを使用する場合はエラーにはなりません。
RDSのセキュリティグループ
RDSインスタンスのドキュメントを見てみると、次のような記載があります。
一見すると、RDSに適用するセキュリティグループはDBSecurityGroups
というプロパティで指定するように見えます。
以下のテンプレートでRDSインスタンスを作成してみます。
AWSTemplateFormatVersion: 2010-09-09
Resources:
# Network
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.16.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value: sample-vpc
# Private Subnet
PrivateSubnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 172.16.0.0/24
AvailabilityZone: !Select
- '0'
- !GetAZs ''
Tags:
- Key: Name
Value: sample-private-1a
PrivateSubnet1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 172.16.1.0/24
AvailabilityZone: !Select
- '1'
- !GetAZs ''
Tags:
- Key: Name
Value: sample-private-1c
# SecurityGroup
RdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: RDS-SG
GroupDescription: allow connections via port 3306
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
VpcId: !Ref VPC
Tags:
- Key: Name
Value: RDS-SG
# Database
RdsInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
DBInstanceIdentifier: my-sample-db
DBName: sampledb
DBSecurityGroups:
- !Ref RdsSecurityGroup
DBSubnetGroupName: !Ref RdsSubnetGroup
Engine: MySQL
EngineVersion: 8.0.23
MasterUsername: testuser
MasterUserPassword: adminpass
MultiAZ: false
PubliclyAccessible: false
StorageEncrypted: false
RdsSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: DB Subnet Group
DBSubnetGroupName: DBSubnetGroup
SubnetIds:
- !Ref PrivateSubnet1a
- !Ref PrivateSubnet1c
しかし、このテンプレートを使用すると、以下のエラーが発生し、スタックの作成に失敗します。
DBSecurityGroupが存在しないと言われています。
CloudFormationの公式ドキュメントによると、DBSecurityGroups
は古いリージョンとの下位互換のために残されている項目であり、現在はVPCSecurityGroups
で指定せよとのことです。
DBSecurityGroups
をVPCSecurityGroups
に置き換えたテンプレートを使うと、スタックの作成に成功します。
# 他は全て同じ
# Database
RdsInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
DBInstanceIdentifier: my-sample-db
DBName: sampledb
VPCSecurityGroups:
- !Ref RdsSecurityGroup
DBSubnetGroupName: !Ref RdsSubnetGroup
Engine: MySQL
EngineVersion: 8.0.23
MasterUsername: testuser
MasterUserPassword: adminpass
MultiAZ: false
PubliclyAccessible: false
StorageEncrypted: false
また、CloudFormation公式ドキュメントのRDSの欄を眺めてみると、AWS::RDS::DBSecurityGroup
というリソースタイプが存在するようです。
一見すると、AWS::EC2::SecurityGroup
ではなく、これを使ってセキリュティグループを作成する必要があるかのように見えます。
しかし、ドキュメントの中身を見てみると、こちらもレガシー環境との下位互換のためだけに存在する項目で、現在は使用しないことが推奨されているようです。
結論としては、RDSに適用するセキュリティグループはAWS::EC2::SecurityGroup
として作成し、DBInstanceのVPCSecurityGroups
プロパティで指定するということになります。
終わりに
このように、CloudFormationには、一見すると正しそうに見える記述なのになぜエラーになるのかわかりにくい要素があります。
ネット上にあるテンプレートを継ぎはぎして作成したり、ドキュメントの表面だけ眺めていると、思わぬエラーに直面しやすくなります。
エラーになった時は、以下のようなことが解決につながりやすいと感じました。
- 記述の間違い/タイポを疑う(特に配列)
- 参照の失敗を疑う
- レガシーな記法である可能性を疑う
- エラーメッセージと公式ドキュメントをよく読む
参考
以下の記事を参考にさせていただきました。
https://dev.classmethod.jp/articles/aws-cloudformation-networkinterfaces-and-instance-level-error/