こんばんは、大畑です。
私事ですが、AWSを学び始めて半年ほどが経ちました。
業務でAWSを使用したクラウド環境の構築を行う際に、CloudFormationというサービスをよく触ります。
CloudFormationはクラウド基盤をコード化する(IaC : Infrastructure as Code)ことで
・Git等を使用したインフラ基盤のバージョン管理ができる
・構築を自動化し、業務効率化できる
・開発環境のクラウド基盤を複製し、ステージング環境、本番環境に同等の構成が作れる
など、様々なメリットを受けることができます。
一方で、yamlやjsonでコードを記述する際や、CloudFormationをコンソールから使用する際にはどうしても人の手が加わります。
これはエラーの原因となりえるため、なるべく排除していきたいところです。
ということで今回は
「ヒューマンエラーの少ないCloudFormationテンプレートの作り方」
について書いていきたいと思います。
【初心者向け】CloudFormationとは
AWS CloudFormation は、インフラストラクチャをコードとして扱うことで、AWS およびサードパーティーのリソースをモデル化、プロビジョニング、管理することができます。
[引用] https://aws.amazon.com/jp/cloudformation/
BlackBelt資料
簡単にまとめると
「AWSを使ったクラウド基盤をコードで構築できるサービス」
となります。
今回修正するCloudFormationテンプレート
今回修正するCloudFormationテンプレートは以下となります。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
InstanceType:
Type: String
Default: "t2.micro"
Description: "Instance type for EC2 instance"
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
Tags:
- Key: "Name"
Value: "MyVPC"
Subnet:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: "Name"
Value: "MySubnet"
InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Allow SSH access to EC2 instances"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 22
ToPort: 22
CidrIp: "0.0.0.0/0"
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
InstanceType: "t2.micro"
ImageId: "ami-06ee4e2261a4dc5c3"
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref Subnet
Tags:
- Key: "Name"
Value: "MyEC2Instance"
VPCとEC2を作成するシンプルなテンプレートとなっています。
ではこちらを修正していきます。
1.パラメータを使用する
CloudFormationではParametersセクションを使うことで、スタック構築時にパラメータを入力することができます。
開発環境(dev)、ステージング環境(stg)、本番環境(pro)でリソース名を分けたい場合、リソースの識別子をパラメータとして定義しておくことで、テンプレートを分けることなく複数環境にデプロイが可能になります。
例えば、EC2の[Name]タグに環境識別子を入れたい場合、ハードコーディングで
CloudFormationテンプレートを書いていると、複数テンプレートを用意する、もしくはその都度環境識別子を修正しなければなりません。
これはテンプレート修正ミスや修正漏れが発生する原因となり得ます。
解決方法として、[Name]タグに入れる環境識別子をパラメータとして置いておくことで、単一のテンプレートで完結することができ、ヒューマンエラーの原因を排除することができます。
他にも、パラメータの[AllowedValues]タグを使用することで、スタック作成時に入力するパラメータをリストから選択させることができます。打ち間違い等がなくなるのでヒューマンエラー対策として有効です。
以下は、Parametersセクションを追加し、修正を加えたCloudFormationテンプレートです。
AWSTemplateFormatVersion: '2010-09-09'
#Parametersでスタック作成時に入力するパラメータを入力する
Parameters:
EnviromentType:
Type: String
Default: "DEV"
AllowedValue: ["DEV","STG","PRO"]
Description: "EnviromentType Allowed DEV,STG,PRO"
AMI:
Type: String
Default: "ami-06ee4e2261a4dc5c3"
AllowedValue: ["ami-06ee4e2261a4dc5c3","ami-01234567890abcdef"]
Description: "AMI for EC2 instance"
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
# ${EnviromentType}でパラメータで入力した値を参照する
Tags:
- Key: Name
Value: ${EnviromentType}-VPC
Subnet:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: ${EnviromentType}-SUBNET01
InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Allow SSH access to EC2 instances"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 22
ToPort: 22
CidrIp: "0.0.0.0/0"
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
InstanceType: "t2.micro"
# ${AMI}でパラメータに入力した値を参照する
ImageId: ${AMI}
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref Subnet
Tags:
- Key: Name
Value: ${EnviromentType}-EC2Instance
2.マッピングファイルを使用する
CloudFormationではMappingsセクションでキーと値を保存することができます。
参照する際は、!FindInMapをResourceセクションで使用することで参照することができます。
これはリージョンを跨いでCloudFormationを実行する際に役立ちます。
例えば、東京リージョンとバージニア北部リージョンでEC2を立てたい場合を考えます。
1で作成したCloudFormationテンプレートの場合、AMIはリージョンで固有のものとなるため、それぞれのリージョンで別のAMIを指定する必要が出てきます。
Mappingファイルを使用するとリージョンに応じてパラメータの値を入力する必要がなくなります。
以下はMappingファイルの例です。
Mappings:
RegionMap:
us-east-1:
"HVM64": "ami-0ff8a91507f77f867"
us-west-1:
"HVM64": "ami-0bdb828fd58c52235"
eu-west-1:
"HVM64": "ami-047bb4163c506cd98"
ap-southeast-1:
"HVM64": "ami-08569b978cc4dfa10"
ap-northeast-1:
"HVM64": "ami-06ee4e2261a4dc5c3"
上記の例では東京リージョンでは「ami-06ee4e2261a4dc5c3」を、バージニア北部リージョンでは「ami-0ff8a91507f77f867」を使用するという記述がされています。
Resourceセクションでは以下のように!FindInMapを使用して、Mappingファイルを参照します。
!FindInMap [RegionMap,!Ref "AWS::Region", HVM64]
以下が1のテンプレートにさらに修正を加えたCloudFormationテンプレートです。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnviromentType:
Type: String
Default: "DEV"
AllowedValues: ["DEV","STG","PRO"]
Description: "EnviromentType Allowed DEV,STG,PRO"
#Mappingsセクションで値を指定する
Mappings:
RegionMap:
us-east-1:
"HVM64": "ami-0ff8a91507f77f867"
us-west-1:
"HVM64": "ami-0bdb828fd58c52235"
eu-west-1:
"HVM64": "ami-047bb4163c506cd98"
ap-southeast-1:
"HVM64": "ami-08569b978cc4dfa10"
ap-northeast-1:
"HVM64": "ami-06ee4e2261a4dc5c3"
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-VPC
Subnet:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-SUBNET01
InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Allow SSH access to EC2 instances"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 22
ToPort: 22
CidrIp: "0.0.0.0/0"
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
InstanceType: "t2.micro"
# FindInMapを使用してMappingファイルを参照する
ImageId: !FindInMap [RegionMap,!Ref "AWS::Region", HVM64]
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref Subnet
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-EC2Instance
3.クロススタック参照を使用する
クロススタック参照とは、複数のスタック間でリソースを参照できるCloudFormationの機能です。
先ほどまでで使っていたテンプレートでは、VPCやサブネット、セキュリティグループといった「ネットワーク」の部分と、EC2の「コンピューティングリソース」で構成されていました。
実際はこの一つのテンプレートでも動きますが、今後別でスタックが増えた場合を考えると、ネットワークとコンピューティングリソースは分けたほうが無難でしょう。
一方で、別のスタックに記述されているリソースはRef関数で参照できなくなるという問題が発生します。もちろん参照できなくなるリソースのIDをパラメータとして入力させるのもいいですが、やはり人の手で入力するとヒューマンエラーが発生する可能性があります。
このような場合に使えるのが、クロススタック参照です。
クロススタック参照を使用するためには、OutPutsセクションでリソースをExportします。
以下は、ネットワークのみのスタックで分割した上で、セキュリティグループとサブネットをExportしたCloudFormationテンプレートです。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnviromentType:
Type: String
Default: "DEV"
AllowedValues: ["DEV","STG","PRO"]
Description: "EnviromentType Allowed DEV,STG,PRO"
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-VPC
Subnet:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-SUBNET01
InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Allow SSH access to EC2 instances"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 22
ToPort: 22
CidrIp: "0.0.0.0/0"
#OutPutsセクションでリソースを別スタックから参照できるようにする
Outputs:
Subnet:
# Valueには参照させたいリソースを記述
Value: !Ref Subnet
# Exportにはどのようなキーで参照させるかを記述
Export:
Name: !Sub ${AWS::StackName}-Subnet
SecurityGroup:
Value: !Ref InstanceSecurityGroup
Export:
Name: !Sub ${AWS::StackName}-SecurityGroup
別のスタックからExportされた値を参照するには、!ImportValue文を使用します。
以下はNW.ymlからExportされたセキュリティグループとサブネットの値を使用して、EC2インスタンスを立てるCloudFormationテンプレートです。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnviromentType:
Type: String
Default: "DEV"
AllowedValues: ["DEV","STG","PRO"]
Description: "EnviromentType Allowed DEV,STG,PRO"
NetworkStackName:
Type: String
Default: "NW"
Description: "NetworkStackName"
Mappings:
RegionMap:
us-east-1:
"HVM64": "ami-0ff8a91507f77f867"
us-west-1:
"HVM64": "ami-0bdb828fd58c52235"
eu-west-1:
"HVM64": "ami-047bb4163c506cd98"
ap-southeast-1:
"HVM64": "ami-08569b978cc4dfa10"
ap-northeast-1:
"HVM64": "ami-06ee4e2261a4dc5c3"
Resources:
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
InstanceType: "t2.micro"
ImageId: !FindInMap [RegionMap,!Ref "AWS::Region", HVM64]
# ImportValueでExportされているリソースのキーを指定する
SecurityGroupIds:
- !ImportValue
'Fn::Sub': '${NetworkStackName}-SecurityGroup'
SubnetId: !ImportValue
'Fn::Sub': '${NetworkStackName}-Subnet'
Tags:
- Key: Name
Value: !Sub ${EnviromentType}-EC2Instance
終わりに
今回はパラメータ、マッピング、クロススタック参照の3つをご紹介しました。
何かシステムを作る際はCloudFormationのようなIaCサービスを利用することがヒューマンエラーを無くす第一歩ですが、その中でもさらに工夫できることがわかりました。
同じリソースを作る場合でも、CloudFormationテンプレートの書き方はたくさんあります。それだけ属人性が高いということです。
参考になれば幸いです。
以上、「ヒューマンエラーの少ないCloudFormationテンプレートの作り方」でした。