前回まで
CloudFormation連載の続きです。
前回は、CloudFormationのテンプレートを使って、AWSインフラ環境に下図のEC2環境を構築しました。
今回はテンプレートファイルの補足として、Parameters、Mappingsを追記することで、一つのテンプレートファイルから複数の条件に対応した環境を構築します。
CloudFormationの基礎知識や、テンプレートの基礎、実行方法は前回の記事をご確認ください。
<テンプレートファイル フォーマット>
AWSTemplateFormatVersion: "version date" Description: String Metadata: template metadata Parameters: # ← 今回はここの話 set of parameters Rules: set of rules Mappings: # ← 今回はここの話 set of mappings Conditions: set of conditions Transform: set of transforms Resources: set of resources Outputs: set of outputs
Parameters とは
Parametersは、Cloud Formationテンプレートに記載するセクションの一つです。
実行時 (スタックを作成または更新するとき) にテンプレートに渡す値を定義するものであり、渡された値はテンプレートの Resources および Outputs セクションで使用することができます。
具体的な例を挙げてみましょう。 前回のテンプレートファイルを見直してみると、これにはVCPのIPアドレスがテンプレート内に固定値(10.0.0.0/21)で書かれています。この状態ですと、IPアドレスの変更要件がある都度、テンプレート自体を書き換える必要があるため、コード保守性が下がってしまいます。
Parametersでは、このような値をテンプレートファイルへのパラメタ(引数)として定義し、スタック作成時にコマンドから指定することができます。
具体的には次のようなコードになります。
AWSTemplateFormatVersion: 2010-09-09 Parameters: # VPC Cidr(Required) cftVpcCidr: Type: String Description: (Required) Enter VPC IP Address. Resources: # VPC cftVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref cftVpcCidr # ここで上のParameterを使う EnableDnsSupport: true
実行時には、デプロイコマンドにオプション --parameter-overrides
を指定してパラメタに設定する値を指定します。
# デプロイコマンド全文 $ aws cloudformation deploy --template-file ./cf.yaml --stack-name cf-tutorial-prd --parameter-overrides cftVpcCidr=10.0.0.0/21
ただ実際の使われかたとして、上のように設定値をParametersで指定することは少なく、次のMappingと合わせて使うことが多いと思います。
Mappings とは
Mappingsも、Cloud Formationテンプレートに記載するセクションの一つであり、キーと値の関係を持つテーブル表の情報です。具体的なマッピングデータを以下に示します。
<環境 Map>
キー | VpcCidr | SubnetCidr | Suffix |
---|---|---|---|
Dev(開発) | 10.0.0.0/21 | 10.0.1.0/24 | dev |
Stg(検証) | 10.1.0.0/21 | 10.1.1.0/24 | stg |
Prd(本番) | 10.2.0.0/21 | 10.2.1.0/24 | prd |
上記は、環境名のキーごとに、VPC・サブネットのIPアドレスと、タグ名に使用されるサフィックスを表にまとめたものです。Cloud FormationのMappingsには上表を定義しておき、実行時には前述Parametersに定義したキーを指定することで、テンプレート内の各項目の値を実行時に決定することができます。ルックアップテーブルのようなものですね。
下にMappingsを使用したテンプレートファイルを記載します。
Parameters: # Environment cftEnv: Type: String Default: Dev # ← Defaultは"Dev"(開発) AllowedValues: # ← 下のいずれかから選択 - Prd - Stg - Dev Description: Enter Prd, Stg or Dev. Default is Dev. Mappings: # Environment Mapping cftEnvMap: Prd: VpcCidr: 10.2.0.0/21 PublicSubnetCidr: 10.2.1.0/24 PrivateSubnetCidr: 10.2.2.0/24 Suffix: prd Stg: VpcCidr: 10.1.0.0/21 PublicSubnetCidr: 10.1.1.0/24 PrivateSubnetCidr: 10.1.2.0/24 Suffix: stg Dev: VpcCidr: 10.0.0.0/21 PublicSubnetCidr: 10.0.1.0/24 PrivateSubnetCidr: 10.0.2.0/24 Suffix: dev Resources: # VPC cftVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, VpcCidr] # ↑ !FindInMap関数でMappingsからVpcCidr値を取得する EnableDnsSupport: true Tags: - Key: Name Value: !Sub - cf-tutorial-vpc-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],}
Mappingsを使用することによって、Parametersで入力された値に応じて、一つのテンプレートファイルで異なる環境の構築を指示することができるようになります。
EC2環境の構築
前回まで作成したテンプレートファイルを、Prd、Stg環境でも使用できるように書き換えました。
▼(テンプレート全文はここをクリックしてください)
AWSTemplateFormatVersion: 2010-09-09 Parameters: # My IP Address(Required) cftMyIpAddress: Type: String Description: (Required)Mapping to Inbound rule on ec2'security group. # Environment cftEnv: Type: String Default: Dev AllowedValues: - Prd - Stg - Dev Description: Enter Prd, Stg or Dev. Default is Dev. Mappings: # Environment Mapping cftEnvMap: Prd: Suffix: prd InstanceType: t3.small VpcCidr: 10.2.0.0/21 PublicSubnetCidr: 10.2.1.0/24 PrivateSubnetCidr: 10.2.2.0/24 Stg: Suffix: stg InstanceType: t3.small VpcCidr: 10.1.0.0/21 PublicSubnetCidr: 10.1.1.0/24 PrivateSubnetCidr: 10.1.2.0/24 Dev: Suffix: dev InstanceType: t3.micro VpcCidr: 10.0.0.0/21 PublicSubnetCidr: 10.0.1.0/24 PrivateSubnetCidr: 10.0.2.0/24 Resources: # VPC cftVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !FindInMap [cftEnvMap, !Ref cåftEnv, VpcCidr] EnableDnsSupport: true Tags: - Key: Name Value: !Sub - cf-tutorial-vpc-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # Internet Gateway cftIgw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub - cf-tutorial-igw-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref cftVpc InternetGatewayId: !Ref cftIgw # Subnet (public) cftPublicSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: true VpcId: !Ref cftVpc CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, PublicSubnetCidr] Tags: - Key: Name Value: !Sub - cf-tutorial-public-subnet-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPublicRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: !Sub - cf-tutorial-public-rtb-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPublicRtb DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref cftIgw cftPublicRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPublicSubnet RouteTableId: !Ref cftPublicRtb # Subnet (private) cftPrivateSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false VpcId: !Ref cftVpc CidrBlock: !FindInMap [cftEnvMap, !Ref cftEnv, PrivateSubnetCidr] Tags: - Key: Name Value: !Sub - cf-tutorial-private-subnet-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPrivateRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: !Sub - cf-tutorial-private-rtb-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftPrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPrivateRtb DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref cftNatgw cftPrivateRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPrivateSubnet RouteTableId: !Ref cftPrivateRtb # NAT Gateway cftNatgw: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt cftNatgwEip.AllocationId SubnetId: !Ref cftPublicSubnet Tags: - Key: Name Value: !Sub - cf-tutorial-natgw-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} cftNatgwEip: Type: AWS::EC2::EIP Properties: Domain: vpc # EC2(public) cftPublicEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: !FindInMap [cftEnvMap, !Ref cftEnv, InstanceType] SubnetId: !Ref cftPublicSubnet Monitoring: false SecurityGroupIds: - !Ref cftPublicEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: !Sub - cf-tutorial-public-ec2-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # SecurityGroup(public EC2) cftPublicEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub - cf-tutorial-public-ec2-sg-${env} - {env: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} GroupDescription: Allow SSH Access. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${privateIp}/32 - {privateIp: !Ref cftMyIpAddress} Tags: - Key: Name Value: !Sub - cf-tutorial-public-ec2-sg-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # ElasicIp for public EC2 cftPublicEc2Eip: Type: AWS::EC2::EIP Properties: InstanceId: !Ref cftPublicEc2 # EC2(private) cftPrivateEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: !FindInMap [cftEnvMap, !Ref cftEnv, InstanceType] SubnetId: !Ref cftPrivateSubnet Monitoring: false SecurityGroupIds: - !Ref cftPrivateEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: !Sub - cf-tutorial-private-ec2-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # SecurityGroup(private EC2) cftPrivateEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub - cf-tutorial-private-ec2-sg-${env} - {env: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix]} GroupDescription: Allow SSH Access from Public EC2 Only. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${privateIp}/32 - {privateIp: !GetAtt cftPublicEc2.PrivateIp} Tags: - Key: Name Value: !Sub - cf-tutorial-private-ec2-sg-${suffix} - {suffix: !FindInMap [cftEnvMap, !Ref cftEnv, Suffix],} # ====================== # Outputs Statement # ====================== # IP Address Allocated to Public EC2 Outputs: cftPublicEc2Eip: Value: !GetAtt cftPublicEc2Eip.PublicIp
Parametersに定義した内容は以下の2つです。
- cftMyIpAddress:SSH接続で接続元となる自端末のグローバルIPアドレスです。
- cftEnv:デプロイする環境です。Dev、Stg、Envのいずれかを指定します。
上記のテンプレートファイルを以下の通り実行することで、環境に応じたVPCがそれぞれ構築されます。(実行時、スタック名がそれぞれ違うものになるように注意してください。同じ名前で実行すると、環境を置き換えてしまうので。)
# 開発(Dev)環境 スタック aws cloudformation deploy --template-file ./cf.yaml --stack-name cf-tutorial-dev --parameter-overrides cftMyIpAddress=xxx.xxx.xxx.xxx cftEnv=Dev # 検証(Stg)環境 スタック aws cloudformation deploy --template-file ./cf.yaml --stack-name cf-tutorial-stg --parameter-overrides cftMyIpAddress=xxx.xxx.xxx.xxx cftEnv=Stg # 本番(Prd)環境 スタック aws cloudformation deploy --template-file ./cf.yaml --stack-name cf-tutorial-prd --parameter-overrides cftMyIpAddress=xxx.xxx.xxx.xxx cftEnv=Prd
<実行後のEC2一覧>
まとめ
以上の通り、Parameters と Mappings を使用することによって、よりIaCらしいテンプレートファイルを作ることができました。環境ごとに手動で構築すると、同じ構成を作っているつもりでも作業ミスが発生することもあリます。
作業効率だけではなく、品質維持のためにも IaC は重宝しますね。
なお、この結果については、以下のGitHubに掲載していますのでご確認ください。 github.com