14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ヒューマンエラーの少ないCloudFormationテンプレートの作り方

Posted at

こんばんは、大畑です。

私事ですが、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テンプレートは以下となります。

templete.yml
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テンプレートです。

templete.yml
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ファイルの例です。

mapping.yml
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テンプレートです。

templete.yml
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テンプレートです。

NW.yml
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テンプレートです。

EC2.yml
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テンプレートの作り方」でした。

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?