再利用可能なテンプレートを心がける
CloudFormationを利用することのメリットには、多くの場合以下の3つが挙げられます。
- 整合性の担保
- 作業リスクの削減
- 作業の効率化
整合性の担保とは、現在の環境がテンプレート通りになっている事。 作業リスクの削減は、文字通り作業ミスを減らせる事。 作業の効率化は、構築時の工数を削減できる事です。
再利用可能なテンプレートを作成することは、テンプレートの修正ミスを減らす事に直結します。 また、環境に合わせてパラメータだけ変える設計にする事で、同じテンプレートで環境を作り分けることが出来、整合性の担保も実現できます。
Parametersセクションの活用
という訳で、デプロイの度に値だけ変えられるように実装していきましょう。 その為にはParametersセクションを利用します。 Parametersセクションは、テンプレートバージョンや、Resourcesセクションの仲間です。
AWSTemplateFormatVersion: "2010-09-09"
Parameters: # <-- New!!
論理名:
Type: データ型
Resources:
Resourceセクション内で「Ref」を使って論理名を呼ぶ事で、入力したパラメータを参照出来ます。
!Ref 論理名
データ型とは、どんな値を入れるかを予め指定しておくものです。 文字列、数値、配列、AWS固有パラメータ等いろいろありますが、まずは文字列(String)を覚えましょう。
Parametersの詳細は、公式リファレンスを参照して下さい。
※ 公式リファレンス: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
実際の利用イメージを確認しましょう。 以下のテンプレートを作成します。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
VpcCidr:
Type: String
Resources:
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
これをCloudFormationコンソールに流し込むと、以下のようにテキストボックスが表示され、値の入力を求められます。
このテキストボックスにパラメータを入力する事で、その値を使ったスタックを作成できるのです。
課題
では、本日の課題です。
- 以下の要件を満たすテンプレートを記述して下さい。また、パラメータは可能な限りデプロイの度に指定できる形で実装して下さい。
- 192.168.0.0/16のCidrを持つVPCを作成する
- 作成したVPC内に、以下セグメントのサブネットを作成する
- 192.168.0.0/24
- Internet Gatewayを作成し、VPCにアタッチする
- ルートテーブルを作成し、Subnetに関連付ける
- ルートテーブルに0.0.0.0/0 -> IGWのルートを書く
- EC2インスタンス用セキュリティグループを作成する
- 作成したサブネット内に、EC2インスタンスを1台起動する
※ インスタンスについてはパラメータが山ほどあるので、一旦誰かのブログを参照してもよいです
解説
こちらです
本日の課題は、Parametersセクションを書いたり、新しいリソースを書いたり、盛りだくさんですね。 基本的な書き方はリファレンスを参照頂くとして、以下のポイントに絞って解説します。
- Parametersセクションの書き方
- 組み込み関数の活用法
Parametersセクションの書き方
まずはParametersセクションに書くものを列挙してみましょう。
- ネットワーク系
- VPCのCidr
- SubnetのCidr
- インスタンス系
- インスタンス名
- キーペア名
- イメージID
- インスタンスタイプ
- ボリュームサイズ
このあたりですかね。 実は、この中のいくつかはTypeとPropertiesを活用すると便利に書けるので、その辺りの解説をしていきます。
- KeyName 「Type: String」で書いても問題ないですが、AWS固有パラメータとして「AWS::EC2::KeyPair::KeyName」が用意されています。 これを使うと、パラメータ入力時に、今あるキーペアをプルダウンから選べるようになります。
KeyName: Type: AWS::EC2::KeyPair::KeyName
- ImageId これもStringでも問題ありません。 しかし、AMIは頻繁にアップデートされる為、都度最新版のIDを引っ張ってくるのって大変ですよね。 AWS固有パラメータの「AWS::SSM::Parameter::ValueAWS::EC2::Image::Id」を使う事で、SSMパラメータストアの値を参照できます。 Systems Managerのパラメータストアで、パブリックパラメータを確認すると、使えるパラメータが並んでいるのが分かります。今回は、Defaultプロパティを使って、以下AMI IDがデフォルトで表示されるようにしておきましょう。
AmiId: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base
- VolumeSize ボリュームサイズには「Type: Number」を指定する事で、数値ならではの制約をかけられるようになります。 例えば、インスタンスのボリュームサイズはOSによって最低値・最大値が決まっています。 MinValue / MaxValueプロパティを使うと、パラメータを制限範囲内に収める事が可能です。 今回は最小値だけ設定してみましょう。
VolumeSize: Type: Number MinValue: 40
組み込み関数の活用方法
前回触れた組み込み関数は、値を参照するものでした。 今回は、文字列を操作するタイプの組み込み関数を使ってみましょう。
- Fn::Sub これは、いわゆる「置換処理」を行う関数です。 Refで参照できるパラメータを、文字列の中に埋め込む時に使えます。 例えば「InstanceName: hmddev」というパラメータがあったとします。 以下のように記述する事で、”hmddev_SecurityGroup”という文字列を作ることができます。
!Sub ‘${InstanceName}_SecurityGroup’
これでインスタンス毎にセキュリティグループ名を生成できますね。
- Fn::Select こちらはちょっとプログラミングっぽい組み込み関数です。 これを使うことで、「配列」というデータ型の中から任意の値を参照できます。 例えば、[hamada, ishihara]というような配列から任意の値を参照するには、以下のように記述します。
# hamadaを参照する場合 !Select - 0 - [hamada, ishihara, hida] # ishiharaを参照する場合 !Select - 1 - [hamada, ishihara, hida]
これを用いて、ちょっと合わせ技を。 「Fn::GetAZs」という組み込み関数で、使えるAzの配列を取得できます。
Fn::GetAZs ap-northeast-1 -> [ap-northeast-1a, ap-northeast-1c, ap-northeast-1d]
ここから1つの値を参照するにはFn::Selectを使えばいいので、
!Select - 0 - Fn::GetAZs ap-northeast-1
これで、サブネットを作成する時、「都度使える東京リージョンのAzを取得して、1つを選択する」というロジックを実現できます! ・・・もう一つだけ言わせて下さい。 AWSは、よく使うAWS系のパラメータをRefで参照できるようにしています。
※ 疑似パラメータ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html
リージョンも「AWS::Region」で参照出来ます。 以下のように書くことで、リージョンへの依存を無くせます。!Select - 0 - Fn::GetAZs !Ref AWS::Region
今度こそ完璧です!
解答例
AWSTemplateFormatVersion: "2010-09-09" Parameters: VpcCidr: Type: String SubnetCidr: Type: String KeyName: Type: AWS::EC2::KeyPair::KeyName InstanceName: Type: String ImageId: Type: AWS::SSM::Parameter::Value Default: "/aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base" InstanceType: Type: String VolumeSize: Type: Number MinValue: 60 Resources: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr InternetGW: Type: AWS::EC2::InternetGateway AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref Vpc InternetGatewayId: !Ref InternetGW Subnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref 'AWS::Region' VpcId: !Ref Vpc CidrBlock: !Ref SubnetCidr RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Route: DependsOn: AttachGateway Type: AWS::EC2::Route Properties: RouteTableId: !Ref RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGW AssociateRouteTable: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref Subnet RouteTableId: !Ref RouteTable SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref Vpc GroupName: !Sub '${InstanceName}_SecurityGroup' GroupDescription: Enable Private access SecurityGroupIngress: - CidrIp: 10.0.0.0/8 IpProtocol: -1 - CidrIp: 172.16.0.0/12 IpProtocol: -1 - CidrIp: 192.168.0.0/16 IpProtocol: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: -1 EC2: Type: AWS::EC2::Instance Properties: ImageId: !Ref ImageId InstanceType: !Ref InstanceType KeyName: !Ref KeyName BlockDeviceMappings: - DeviceName: '/dev/sda1' Ebs: VolumeType: gp3 VolumeSize: !Ref VolumeSize DeleteOnTermination: true NetworkInterfaces: - AssociatePublicIpAddress: false DeviceIndex: '0' SubnetId: !Ref Subnet GroupSet: - !Ref SecurityGroup Tags: - Key: Name Value: !Ref InstanceName
かなり立派なスクリプトになってきましたね。 次回はテンプレートの分割について勉強していきます。 お楽しみに! 以上、エンジニアの濱田でした!