はじめに
突然ですがCloudFormationを今まで体系的に勉強したことがなかったので、お盆休みを使ってまとめてみました。
目次
- 前提条件
- CloudFormationとは?
- CloudFormation基礎用語
- とりあえず動かしてみよう
- パラメーターを定義してスタックを作成しよう
- Mappingsを使って汎用性の高いテンプレートを作成しよう
- 組み込み関数について理解しよう
- 本格的なインフラを構築しよう
- おわりに
前提条件
CloudFormationとは?
CloudFormationはAWSリソースの構成管理ツールです。jsonやymlといったテキスト形式でAWSリソースを宣言的に記述することで、AWS上でのインフラストラクチャの構築・削除を自動化することができます。AWSリソースをコードで記述しておくことで、インフラのアーキテクチャを簡単に把握することができます。例えば、開発チームに新人が入ってきたとしてもCloudFormationのテンプレートを見れば容易にアーキテクチャの概要を知ることができます。また、CloudFormationでインフラを構築していれば、既存の環境を変更することができ、その変更箇所も容易に把握することができます。私個人のユースケースとしては、検証用途に作った環境をいつでも再現できるようにするために利用しています。会社の人に「あの環境すぐ作れる?」って聞かれたら「秒でつくれます」とドヤれます。めっちゃ便利です。
CloudFormation基礎用語
テンプレート
EC2やVPCなどのAWSリソースが定義されたテキストファイルのことです。このファイルに作成したいリソースやリソースの設定など記述します。CloudFormationではjsonとyamlをサポートしています。(この記事ではymlを使用します。というか特別な理由がなければyamlを使うことをお勧めします。)
スタック
テンプレートから作成されたAWSリソース群のことです。テンプレートと何が違うの?とツッコミをいれそうになった人は、テンプレートはテキストファイルそのもの、スタックはAWSリソース、くらいの認識で大丈夫かと思います。
CloudFormationの大枠を掴むにはこの二つを理解していれば大丈夫です。次からは実際にテンプレートを記述していきましょう。
とりあえず動かしてみよう
スタックの作成
何はともあれ、まずは実際に手を動かしてスタックを作成してみましょう。以下の内容が書かれたymlファイルを作成してください。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Resources:
MyFirstInstance: #任意のリソース名
Type: "AWS::EC2::Instance"
Properties: #EC2インスタンスの詳細設定
ImageId: "ami-0c3fd0f5d33134a76"
InstanceType: t2.micro
Tags:
- Key: "Name"
Value: "test-ec2"
KeyName: "mykey" #EC2ダッシュボードから任意のキーペアを作成してください。
BlockDeviceMappings:
-
DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
CloudFormationのダッシュボードから【スタックの作成】>【テンプレートファイルのアップロード】>【ファイルのアップロード】を選択し、先ほど作成したテンプレートをアップロードしましょう。【次へ】を選択して任意のスタック名を入力してください。次のオプションのスタック設定ではデフォルトのままにしておき、最後に【スタックの作成】を選択します。数秒後、EC2ダッシュボードに移動してみるとEC2インスタンスが作成されていることが確認できます。
スタックの変更
次にスタックを変更してみましょう。インスタイプをt2.microからt2.nanoに変更します。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Resources:
MyFirstInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: "ami-0c3fd0f5d33134a76"
InstanceType: t2.nano #t2.nanoに変更
Tags:
- Key: "Name"
Value: "test-ec2"
KeyName: "mykey"
BlockDeviceMappings:
-
DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
先ほどのスタックの詳細画面から【スタックの変更】を選択して新しいテンプレートを再アップロードしてください。【スタックの更新】を選択し、数秒後EC2ダッシュボードから確認するとインスタンスサイズがt2.microからt2.nanoに変更されています。
スタックの削除
最後にリソースのクリーンアップを行いましょう。スタックの詳細画面から【スタックの削除】を選択し、削除してください。
ここまでやってみていかがだったでしょうか?作成したリソースがEC2インスタンスだけなので、まだCloudformationの恩恵を感じにくいかもしれません。最後のセクションでもう少し複雑な構成のテンプレートをつくっていきます。
Parametersを定義してスタックを作成しよう
次にParametersを使ってテンプレートを作成していきましょう。Parametersとはプログラミングで言う所の変数のようなものでテンプレート内であらかじめ宣言しておくことでスタックの作成時に宣言したParametersの中から動的に値を選択することがきます。以下の内容を見てください。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Parameters: #Parametersの定義
InstanceParameter:
Type: String
Default: t2.micro #何も選択しなかった場合の選択肢
AllowedValues:
- t2.micro
- t2.small
- t2.nano
Description: select instancetype #コンソールでの操作時にこの内容が表示される
Resources:
MyFirstInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: "ami-0c3fd0f5d33134a76"
InstanceType: !Ref InstanceParameter #Parametersで定義した値を参照
Tags:
- Key: "Name"
Value: "test-ec2"
KeyName: "mykey"
BlockDeviceMappings:
-
DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
Parametersのセクションで複数のインスタンスタイプを宣言しています。こうすることによって、開発環境や本番環境でインスタンスタイプを変更したい場合などにわざわざ別のテンプレート作成せずに同じテンプレートを使い回すことができます。では実際にスタックを作成していきます。
ファイルをアップロードして次のような画面が出てくれば成功です。ここで何も選択しなかった場合はデフォルト値として定義したt2.microでインスタンスが作成されます。
Mappingsを使って汎用性の高いテンプレートを作成しよう
次にMappingsを使ってテンプレートを作成していきます。AWSをかじったことのある方ならご存知かもしれませんが、EC2インスタンスのAMI-IDはリージョンによって異なっています。リージョンごとに適切なAMI-IDが記述されたテンプレートを用意するのは面倒です。そこでMappingsの出番です。Mappingsセクションにキーバリュー形式で値を宣言し、FindInMap関数で参照することができます。実際のテンプレートを見てみましょう。この例では東京リージョンとバージニアリージョンでスタックを作成することを想定しています。このテンプレートを実行すると東京リージョンにはAmazon Linux2、バージニアにはUbuntuのインスタンスが作成されます。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Mappings:
RegionMap:
us-east-1:
hvm: "ami-07d0cf3af28718ef8" # 東京リージョンではAmazon Linux
ap-northeast-1:
hvm: "ami-0c3fd0f5d33134a76" # バージニアリージョンではUbuntu
Resources:
MyFirstInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", hvm] #FindInMap関数でリージョンの参照。このテンプレートがどのリージョンで実行されるかによって作成される関数が変わってくる。
InstanceType: t2.micro
Tags:
- Key: "Name"
Value: "test-ec2"
KeyName: "mykey"
BlockDeviceMappings:
-
DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
EC2のダッシュボードで確認すると、リージョンによって、OSが異なっていますね。
- 東京リージョン
- バージニア北部リージョン
組み込み関数を理解しよう
次に組み込み関数を使ってテンプレートを作成していきましょう...と言いたいところなのですが、実はもう組み込み関数使ってます。RefとFindInMapってやつですね。RefはParametersを参照するときに使って、FindInMapはMappingsを参照するときに使います。個人的にこの2つを使いこなせれば問題ないと思うのですが、気になる方は公式ドキュメントを参照してみるといいかもです。(手抜きしてすいません...)
本格的なインフラを構築しよう
ここまでひたすらEC2インスタンスを作成してきましたが、セキュリティグループも定義してないし、VPCもでフォルダだしこれだと検証用途にも使えないと思います。なので、このセクションではこれまでやってきたことを使って、もう少し本格的なインフラを構築していきます。インフラの概要を図で見ていきましょう。VPCの中にサブネットを作成し、その中に2つのEC2インスタンスを作成しています。これまで作成したEC2はセキュリティグループが設定されておらず、SSH接続ができませんでしたが、このテンプレートではセキュリティグループを作成し、0.0.0.0/0つまり全てのホストからのアクセスを許可しています。これで秘密鍵を持っているクライアントはこのEC2インスタンスにアクセスできるようになります。スタックを作成したらテンプレートの設定が反映されているか確認してみてください。
AWSTemplateFormatVersion: "2010-09-09"
Description: A sample template
Parameters:
FirstInstanceParameter: #1つ目のインスタンス用のパラメーター
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.nano
Description: select instancetype
SecondInstanceParameter: #2つ目のインスタンス用のパラメーター
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.nano
Description: select instancetype
Mappings:
RegionMap:
us-east-1:
hvm: "ami-07d0cf3af28718ef8" # 東京リージョンではAmazon Linux
ap-northeast-1:
hvm: "ami-0c3fd0f5d33134a76" # バージニアリージョンではUbuntu
Resources:
# ネットワーク系のリソース
MyVpc:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: '10.0.0.1/16'
Tags:
- Key: 'Name'
Value: 'MyVpc'
MySubnet:
Type: 'AWS::EC2::Subnet'
Properties:
CidrBlock: '10.0.10.0/24'
MapPublicIpOnLaunch: true
Tags:
- Key: 'Name'
Value: 'MySubnet'
VpcId: !Ref MyVpc
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: 'Name'
Value: 'MyIGW'
MyAttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MyVpc
InternetGatewayId: !Ref MyInternetGateway
MyRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: 'Name'
Value: 'MyRoute'
VpcId: !Ref MyVpc
MyRoute:
Type: AWS::EC2::Route
DependsOn: MyInternetGateway
Properties:
RouteTableId: !Ref MyRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref MyInternetGateway
MySubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref MySubnet
RouteTableId: !Ref MyRouteTable
# EC2インスタンス群
MyFirstInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", hvm]
InstanceType: !Ref FirstInstanceParameter
Tags:
- Key: "Name"
Value: "first-test-ec2"
KeyName: "mykey"
SubnetId: !Ref MySubnet
SecurityGroupIds:
- !Ref MySecurityGroup
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
MySecondInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", hvm]
InstanceType: !Ref SecondInstanceParameter
Tags:
- Key: "Name"
Value: "second-test-ec2"
KeyName: "mykey"
SubnetId: !Ref MySubnet
SecurityGroupIds:
- !Ref MySecurityGroup
BlockDeviceMappings:
- DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
# セキュリティグループ
MySecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "MySecurityGroup"
VpcId: !Ref MyVpc
Tags:
- Key: 'Name'
Value: 'MySG'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
おわりに
CloudFormationを使う前まではTerraform信者だったんですが、AWSで使うとなるとやっぱりCloudFormation一強ですね。最後に作成したテンプレートはネットワーク系のリソースとEC2のリソースをひとまとめにしたのですが、インフラの規模が大きくなると可読性が落ちるのでリソースの種類によってテンプレートを分けると良さそうです。
参考資料:公式ドキュメント