CloudFormationでFn::ImportValue組み込み関数を使う(VPC,EC2の作成)
こんにちは株式会社ベンジャミンの木村直紀です!
すっかり暖かくなり、春を感じる今日この頃です。2022年のRe:Inventに参加してから、周りのAWSエンジニアに感化され、資格取得に燃えており、現在AWS資格のAWS DevOps Professional取得に向けて勉強しております!
今回、AWS DevOps Professionalの勉強によく出てくるCloudFormationの理解のため、Fn::ImportValue組み込み関数を使ってリソースの構築をしてみましたので、その利点や記述方法について見ていければと思います!
Fn::ImportValue組み込み関数とは?
そもそもFn::ImportValue組み込み関数とは何なのでしょうか?
AWS公式ページには以下のように記載があります。
Fn::ImportValue 組み込み関数は、別のスタックによってエクスポートされた出力の値を返します。 この関数は通常、クロススタック参照を作成するために使用されます。
Fn::ImportValue – AWS CloudFormation
Fn::ImportValue組み込み関数を使えば、作成したスタックの値を別のスタックでも使えるようになり、リソースごとにファイルを分けて記述できるようになるようです。
それではVPC、EC2の作成を例にFn::ImportValue組み込み関数を使わない記述、使う記述を両方見て何が違うのかを見ていきたいと思います。
Fn::ImportValue組み込み関数を使わずにVPC、EC2を作成する
ファイル名:vpc.yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
TestVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: "TestVPC"
TestPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref TestVPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "TestPublicSubnet"
TestPrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref TestVPC
CidrBlock: "10.0.2.0/24"
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: "TestPrivateSubnet"
TestInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "TestInternetGateway"
TestGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref TestVPC
InternetGatewayId: !Ref TestInternetGateway
TestRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref TestVPC
Tags:
- Key: Name
Value: "TestRouteTable"
TestPublicRoute:
Type: AWS::EC2::Route
DependsOn: TestGatewayAttachment
Properties:
RouteTableId: !Ref TestRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref TestInternetGateway
TestPrivateRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref TestRouteTable
SubnetId: !Ref TestPrivateSubnet
TestPublicRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref TestRouteTable
SubnetId: !Ref TestPublicSubnet
TestVpcInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-02a2700d37baeef8b
InstanceType: t2.micro
KeyName: testkey
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
SubnetId: !Ref TestPublicSubnet
GroupSet:
- !Ref TestSecurityGroup
TestSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow SSH access from anywhere
VpcId: !Ref TestVPC
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 22
ToPort: 22
※上記の記述はあくまでサンプルです。Fn::ImportValue組み込み関数の動きを確認するために用意したものなので、ご使用の際はサンプルだと言うことをご理解の上お願いいたします。
※TestPublicSubnetやTestPrivateSubnetのプロパティにあるMapPublicIpOnLaunchは、PublicIPを自動でつけるかどうかをture,falseで表しております。
※GatewayAttachmentリソースはInternetGatewayをVPCにアタッチするために記載しております。
作成しているリソースはVPC、PublicSubnet、PrivateSubnet、InternetGateway、RouteTable、EC2(パブリック)、SecurityGroupになります。今回、作成しているリソースはEC2作成に最低限必要なリソースを用意しました。VPCやEC2の細かい記述については触れませんが、上記のEC2を作成するための記述を別のファイルに移動したら、どんな記述が必要かを確認していきたいと思います。
Fn::ImportValue組み込み関数を使ってVPC、EC2を作成する
まずはEC2を作成する記述を別のファイルに移動した後、vpc.yamlの中身をどう変えないといけないかを見ていきます。
ファイル名:vpc.yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
TestVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: "TestVPC"
TestPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref TestVPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "TestPublicSubnet"
TestPrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref TestVPC
CidrBlock: "10.0.2.0/24"
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: "TestPrivateSubnet"
TestInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "TestInternetGateway"
TestGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref TestVPC
InternetGatewayId: !Ref TestInternetGateway
TestRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref TestVPC
Tags:
- Key: Name
Value: "TestRouteTable"
TestPublicRoute:
Type: AWS::EC2::Route
DependsOn: TestGatewayAttachment
Properties:
RouteTableId: !Ref TestRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref TestInternetGateway
TestPrivateRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref TestRouteTable
SubnetId: !Ref TestPrivateSubnet
TestPublicRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref TestRouteTable
SubnetId: !Ref TestPublicSubnet
# TestVpcInstance:
# Type: AWS::EC2::Instance
# Properties:
# ImageId: ami-02a2700d37baeef8b
# InstanceType: t2.micro
# KeyName: testkey
# NetworkInterfaces:
# - AssociatePublicIpAddress: true
# DeleteOnTermination: true
# DeviceIndex: 0
# SubnetId: !Ref TestPublicSubnet
# GroupSet:
# - !Ref TestSecurityGroup
TestSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow SSH access from anywhere
VpcId: !Ref TestVPC
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 22
ToPort: 22
Outputs:
SecurityGroupIds:
Value: !Ref TestSecurityGroup
Export:
Name: TestVpcStack-TestSecurityGroup
SubnetId:
Value: !Ref TestPublicSubnet
Export:
Name: TestVpcStack-TestPublicSubnet
EC2作成部分の記述は別ファイルに移動したいため、TestVpcInstanceの記述はコメントアウトしました。
また、EC2作成の際に必要な値であるSecurityGroupのID、PublicSubnetのIDはvpc.yamlに残ります。これらはOptputsセクションのExportに記述しております。このように記述することでEC2を作成するファイルではOutputからExportされた値を移動先のファイル(後で出てくるec2.yaml)で扱うことができます。
※ExportのNameはAWS アカウントごとに、リージョン内で一意である必要があります。
次にec2の作成ファイルを見ていきます。
ファイル名:ec2.yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
TestVpcInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-02a2700d37baeef8b
InstanceType: t2.micro
KeyName: testkey
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
SubnetId: !ImportValue TestVpcStack-TestPublicSubnet
GroupSet: [!ImportValue TestVpcStack-TestSecurityGroup]
※GroupSetのところは[ ]付きで記述しないと「Value of property GroupSet must be of type List of String」とエラー文が出ます。
先ほど同じファイルでRef!と記載されていたところが!ImportValueになっております。TestVpcStack-TestPublicSubnetやTestVpcStack-TestSecurityGroupは先ほどのvpc.yamlでExportしたNameを記述しています。
このようにFn::ImportValue組み込み関数を使うことで、リソースごとにファイルを分けて記述することが可能になりました。
AWS DevOps Professionalの資格勉強をしているとFn::ImportValue組み込み関数を問う問題で、「論理部分を別ファイルに記述するにはどうすればいいか?」や今回のような「VPCとEC2の記述を分けるにはどうすればいいか?」と聞かれますので、参考にしていただければと思います。
おまけ(つまずいたところ)
NetworkInterfacesを指定するときの設定
Resources:
TestVpcInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: ami-0c3fd0f5d33134a76
KeyName: testkey
SecurityGroupIds: !Ref TestSecurityGroup
SubnetId: Ref! TestPublicSubnet
NetworkInterfacesを指定していないときは上記のようにセキュリティグループはSecurityGroupIdsでSecurityGroupのIDを指定して記述します。
ただEC2に紐づけるネットワークインターフェースの設定を行いたいときは下記のように、パラメータを増やさなくてはなりません。このときエラーが起こった…
Resources:
TestVpcInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c55b159cbfafe1f0
InstanceType: t2.micro
KeyName: mykey
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
SubnetId: Ref! TestPublicSubnet
SecurityGroupIds: !Ref TestSecurityGroup
上記のように、NetworkInterfacesを指定したときに、セキュリティグループを別で記述すると下記のようなエラーが出てきてしまいました。
Network interfaces and an instance-level security groups may not be specified on the same request
翻訳すると「ネットワーク インターフェイスとインスタンス レベルのセキュリティ グループを同じリクエストで指定することはできません」となります。つまり、セキュリティグループとネットワークインスタンスは同じパラメータとして横並びで記述できませんよと言うことなのです。
では、セキュリティグループを記述するにはどうすればいいのでしょうか?
それは、下記のようにNetworkInterfacesの値の一部として記述すればいいのです。
Resources:
TestVpcInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c55b159cbfafe1f0
InstanceType: t2.micro
KeyName: mykey
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
DeviceIndex: 0
SubnetId: Ref! TestPublicSubnet
GroupSet: !Ref TestSecurityGroup
同じEC2 を作成する記述でも、少し記述を変えるだけでエラーが出てくるのは少し厄介ですね…
これを読んでくれた方が、少しでも問題を解決していだければと思います。