見出し画像

CloudFormationのマッピング機能を使って理想のInfrastucture as Codeに近づける

こんにちは。DevOps推進1課に所属しています Sasagawaです。

AWS CloudFormationのマッピング機能を利用して理想のInfrastucture as Code(IaC)に近づける方法についてご紹介します。
Infrastructure as Codeはインフラのコード化、インフラをコードで管理することですが、それにより以下のメリットがあります。

  • 本番環境/ステージング環境/開発環境などバージョンレベルまで同じ環境を構築できる。

  • 大量のサーバ群を構築してもヒューマンエラーが発生しない。

ちなみにAWS公式サイトではCloudFormationを以下のように説明しています。

AWS CloudFormation によって、AWS 関連リソースおよびサードパーティーリソースの集合体を Infrastructure as Code として扱うことでモデリングし、高速かつ安定的にプロビジョニングし、ライフサイクル全体にわたって管理することが容易になります。

Excelで作った手順書で構築してもいいですが、こんなことはよくありますよね??
「Excelファイルの更新日付を確認したら3年前に更新が止まっていた…」
「自分が正しいと思う手順に勝手に変えるメンバーがいる…しかも障害が発生して調査後に発覚する。」

AWS CloudFormationを利用することでインフラの構築がコード化できるので誰がやっても同じ結果になります。
しかも何度繰り返しても同じ結果になります。(冪等性(べきとうせい))

しかしCloudFormationでコード化するとしても、運用していく中で本番環境、ステージング環境、開発環境でコードの違いによる環境の差異が出てきたら本末転倒です。
初期メンバーは分かっていてもあとから参加したメンバーが見た時に「何でこんなことしているんだろう・・・」って悩んだらせっかくコード化したメリットが消えてしまいます。

実はCloudFormationにはその環境の差異を吸収してくれるマッピング(Mappings)機能があります。
CloudFormationのテンプレートファイル1つに全環境を並べてコード化できるのでプログラムをかじったことがあればすぐに理解できますし、テンプレートファイルの運用管理も楽になります。
環境ごとに1つずつファイルを作って運用管理する必要がなくなります。

前置きが長くなりましたがマッピング機能を使って理想のInfrastucture as Codeに近づける方法をご紹介します。

CloudFormationのテンプレートは構造化されておりセクションで分けられています。マッピングはMappingsセクションで「キー」と「値」をマッピングします。

実際にやってみましょう。
マッピングする場合は3つのパラメータが必要になります。
分かりやすくシンプルに記載してみました。以下のようなイメージになります。

VpcSubnetMapping:
  Prd:
    Subnet: 10.0.1.0/24
  Stg:
    Subnet: 10.0.2.0/24
  Dev:
    Subnet: 10.0.3.0/24

いかがでしょうか?
プログラムが苦手な方でも初見で理解できるかと思います。

  • Prd環境のサブネット:10.0.1.0/24

  • Stg環境のサブネット:10.0.2.0/24

  • Dev環境のサブネット:10.0.3.0/24

分かりやすいですよね。
きれいに並んでいるので一目で理解できます。

VpcSubnetMappingはマッピングの名前になります。
ここで、どのマッピング(VpcSubnetMapping)からキー(Prd等)と値(Subnet: 10.0.1.0/24等)を拾ってくるか指定します。
例えばPrdはキーでキーによって名前と値が変わります。

Subnet: 10.0.1.0/24は名前と値です。
ここが一番混乱するポイントですが名前と値が1セットになっていることに注意してください。

■構文

   名前: 値

■例

   Subnet: 10.0.1.0/24



マッピング機能により解決される課題

ここでマッピング機能により解決される課題を上げてみます。

  • 本番環境とステージング環境で別々のテンプレートを作らなくてもよい
    →本番環境とステージング環境が同じ環境になります。

  • 環境ごとに別々の設定ができる
    →上記と矛盾してしまいますが、Dev環境だけインスタンスタイプを変えるなどコスト削減などその他の課題も解決できます。

  • 要するに環境ごとに変更してはいけないものは変更できないようにし、目的があって変更したいものは変更できる
    →設計通りに環境を構築できるます。しかもテンプレートの再利用性を意識した設計ができます。

  • 該当箇所までスクロールする必要がないので修正するのが簡単になる
    →何気に重要なポイントです。テンプレートが長くなると該当箇所までスクロールして修正するのは面倒です。上部のセクションですべてを設定できると修正が楽になり品質が上がります。

実際にマッピング機能を使ってテンプレートを作ってみる

実際にやってみましょう。
テンプレートのフォーマットはYAMLとJSONフォーマットを選択できます。
可視性とコメントを書くことができるのでYAML形式をお勧めします。

【参考例】 シンプルな基本編
環境ごとにEC2インスタンスタイプを指定しています。
コストを意識して本番環境はスペックを高く、開発環境はスペックが低いインスタンスを選択してコスト削減をしています。

  • Prd環境:t2.large

  • Stg環境:t2.small

  • Dev環境:t2.micro

Parameters:
  Environment:
    Description: Type of Environment.
    Type: String
    AllowedValues:
      - Prd
      - Stg
      - Dev
    Default: Dev

Mappings:
  InstanceTypeEnvironmentMapping:
    Prd:
      InstanceType: t2.large
    Stg:
      InstanceType : t2.small
    Dev:
      InstanceType : t2.micro

Resources:
  SampleEC2Instance:
    Type: AWS::EC2::LaunchTemplate
      Properties:
        InstanceType : !FindInMap [InstanceTypeEnvironmentMapping, !Ref Environment, InstanceType]

以下、吹き出しでコメントを入れています。

【参考例】 応用編
こちらも環境ごとに設定しています。
キーで構成を並べることにより一目で環境が把握できます。

Parameters:
  Environment:
    Description: Type of Environment.
    Type: String
    AllowedValues:
      - Prd
      - Stg
      - Dev

Mappings:
  EnvironmentMapping:
    Prd:
      VpcCidr: 10.0.1.0/24
      InstanceName: prd-linux
      InstanceType: t2.large
    Stg:
      VpcCidr: 10.0.2.0/24
      InstanceName: stg-linux
      InstanceType : t2.small
    Dev:
       VpcCidr: 10.0.3.0/24
       InstanceName: dev-linux
       InstanceType : t2.micro

Resources:
  SampleEC2Instance:
    Type: AWS::EC2::LaunchTemplate
      Properties:
        InstanceType : !FindInMap [EnvironmentMapping, !Ref Environment, InstanceType]

以下、吹き出しでコメントを入れています。

スタックの中でMappingsセクションを配置する場所はどこでもいいですが、ParametersとResourcesセクションの間に配置すると分かりやすいです。

マッピングの利用方法

今回のCloudFormationのスタックでは環境ごとに分けていますが、マッピングした値は!FindInMap関数で取得します。

!FindInMap関数で値を取得する際は3つのパラメータが必要になります。

構文

!FindInMap [MapName, TopLevelKey, SecondLevelKey]

例えば以下の関数を例に説明します。
InstanceTypeに設定するインスタンスタイプを取得します。

InstanceType : !FindInMap [EnvironmentMapping, !Ref Environment, InstanceType]

3つのパラメータ

  • MapName:EnvironmentMapping

  • TopLevelKey: Environment(PrdとかStgなど)

  • SecondLevelKey:InstanceType(t2.largeなど)

例えば、TopLevelKeyがPrdの場合は、インスタンスタイプはt2.largeになります。

「じゃあ、どうやってPrdとかStgとかが決まるの?」
CloudFormationでスタックを作成する際にパラメータ(PrdとかStgなど)を渡します。
AWSマネジメントコンソールでもパラメータを渡せますが、今回は理想のIaCに近づけるがテーマなのでコマンドラインから実施してみます。

コマンドで環境のパラメータを渡してCloudFormationのスタックを作成する

※各環境にAWS CLIがインストールされている前提です。

コマンドの構文
※LinuxとPowerShellの違いはコマンドの改行が「\」か「`」の違いだけです。

■Linuxの場合
aws cloudformation create-stack \
 --stack-name [スタック名] \
 --template-url https://[S3バケット名].s3.ap-northeast-1.amazonaws.com/[テンプレートファイル名] \
 --parameters ParameterKey=Environment,ParameterValue=[環境]

■PowerShellの場合
aws cloudformation create-stack `
 --stack-name [スタック名] `
 --template-url https://[S3バケット名].s3.ap-northeast-1.amazonaws.com/[テンプレートファイル名] `
 --parameters ParameterKey=Environment,ParameterValue=[環境]

例:Prd環境をパラメータで渡したい場合

  • スタック名:test-stack

  • バケット名:test-bucket

  • テンプレートファイル名:test-env.yml

■Linuxの場合
aws cloudformation create-stack \
 --stack-name test-stack \
 --template-url https://test-bucket.s3.ap-northeast-1.amazonaws.com/test-env.yml \
 --parameters ParameterKey=Environment,ParameterValue=Prd

■PowerShellの場合
aws cloudformation create-stack `
 --stack-name test-stack `
 --template-url https://test-bucket.s3.ap-northeast-1.amazonaws.com/test-env.yml `
 --parameters ParameterKey=Environment,ParameterValue=Prd

実際に実行する

個人環境で実際に実行してみます。
従量課金なので構築後にすぐにスタックを削除すればほぼ料金は発生しません。

■実際に使用したテンプレート
テンプレートファイル名:test-env.yml

AWSTemplateFormatVersion: 2010-09-09
Description: Sample Template in CloudFormation
Parameters:
  Environment:
    Description: Type of Environment
    Type: String
    AllowedValues:
      - Prd
      - Stg
      - Dev

Mappings:
  EnvironmentMapping:
    Prd:
      VpcCidr: 10.0.1.0/24
      InstanceName: prd-web-server
      InstanceType: t2.large
    Stg:
      VpcCidr: 10.0.2.0/24
      InstanceName: stg-web-server
      InstanceType : t2.micro
    Dev:
       VpcCidr: 10.0.3.0/24
       InstanceName: dev-web-server
       InstanceType : t2.nano

Resources:
  SampleVpc:
    Type: AWS::EC2::VPC
    Description: Sample VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: Sample VPC

  SampleSubnet:
    Type: AWS::EC2::Subnet 
    Properties:
      CidrBlock: !FindInMap [EnvironmentMapping, !Ref Environment, VpcCidr]
      MapPublicIpOnLaunch: true
      VpcId: !Ref SampleVpc

  SampleRouteTable:
    Type: AWS::EC2::RouteTable 
    Properties:
      VpcId: !Ref SampleVpc

  SampleInternetGateway:
    Type: AWS::EC2::InternetGateway

  SampleGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref SampleVpc
      InternetGatewayId: !Ref SampleInternetGateway

  InternetRoute:
    Type: AWS::EC2::Route 
    DependsOn:
      - SampleGatewayAttachment
    Properties:
      RouteTableId: !Ref SampleRouteTable
      GatewayId: !Ref SampleInternetGateway
      DestinationCidrBlock: 0.0.0.0/0

  SampleSubnetRouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref SampleRouteTable
      SubnetId: !Ref SampleSubnet

  SampleInstance:
    Type: AWS::EC2::Instance
    DependsOn:
      - InternetRoute
      - SampleSubnetRouteTableAssoc
    Properties:
      InstanceType: !FindInMap [EnvironmentMapping, !Ref Environment, InstanceType]
      KeyName: xxxxxxxx
      SubnetId: !Ref SampleSubnet 
      ImageId: ami-xxxxxxxxxxxxxxxx
      SecurityGroupIds:
        - !Ref SampleSecurityGroup
      Tags:
        - Key: Name
          Value: !FindInMap [EnvironmentMapping, !Ref Environment, InstanceName]

  SampleSecurityGroup:
    Type: AWS::EC2::SecurityGroup 
    Properties:
      GroupDescription: Sample Security Group 
      VpcId: !Ref SampleVpc
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0 
          IpProtocol: icmp
          FromPort: -1
          ToPort: -1
        - CidrIp: 0.0.0.0/0 
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22

■コマンド実行

■PowerShellの場合
aws cloudformation create-stack `
  --stack-name test-stack `
  --template-url https://xxxxxxxxxx.s3.ap-northeast-1.amazonaws.com/Test-Template.yml `
  --parameters ParameterKey=Environment,ParameterValue=Prd
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/test-stack/xxxxxxxxxxxxxx"
}

1分後くらいに下図のように完成しました。

このようにマッピング機能を利用することでコード上より各環境の差分を一目で確認でき、且つ他の部分は全環境で統一することができるようになります。

例として環境ごとに異なる部分を抽出してテンプレートを作成しましたが、例えばAMIなどの可変の部分もパラメータとして引き渡すことができます。
いろいろ工夫して理想のInfrastucture as Codeを実現していきましょう!

最後にですが、AWSは従量課金なので今回のスタックを作成すると課金されます。その為試用の方は動作確認後にスタックの削除をしましょう。スタックを削除することでCloudFormationで構築した環境は綺麗に削除されます。

_________________________________

執筆者プロフィール:Sasagawa
大規模ECサイトにてCI/CDパイプラインの運用に従事。
AWS CodePileline、CodeDeploy、CodeBuild、CodeCommit、CloudFormation、Lambda、Auto ScalingなどでCI/CDパイプラインの構築経験。AnsibleやServerspecでインフラ構築とテストの自動化なども経験。
趣味は町中を散歩など。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/