AWS
CloudFormation

CloudFormationのススメ

概要

CloudFormationとは

公式曰く、

AWS CloudFormation は Amazon Web Services リソースのモデル化およびセットアップに役立つサービスです。
AWS CloudFormation とは - AWS CloudFormation

らしいです。難しいですね。
ざっくりいうと AWSリソースをコード管理 するためのツールです。
所謂「 Infrastructure as Code 」ってやつです。

公式で発表されるアーキテクチャはCloudFormationで書かれる事が多いので、
AWSを触れるエンジニアは書けなくても読めることで選択の幅が広がるのではないでしょうか
(例えば これ とか これ など)

CloudFormationの読み方

フォーマット

CloudFormationは json と yaml で記述することが可能です。
特に理由がない限りyamlの方が可読性に優れるため、yamlを選択するのがベターです。

テンプレートの構成

テンプレートは 9個の主要セクション で成り立っていますが、多くのユースケースで必要なのは以下の 5つ で一般的なユースケースは満たされるはずです。

AWSTemplateFormatVersion: '2010-09-09'
Description:
Parameters:
Resources:
Outputs:

一つずつ見ていきましょう。

AWSTemplateFormatVersion

CloudFormationのバージョンを指定。
2017年12月現在 '2010-09-09' が唯一のバージョンなので決め打ち。

Description

テンプレート実行時に表示される説明。
このセクションはコードに影響は及ぼしませんが、後から引き継ぐ人のためにも書いてあげた方が親切でしょう。
設定した値がテンプレートの実行時に 説明 カラムに表示されます。
Screen Shot 2017-12-22 at 22.50.40.png

Parameters

ここでパラメータを宣言するとテンプレート実行時に任意の値を宣言することができます。
この値はResourcesでAWSリソースを作成する際に用いることができます

Webコンソールからパラメータ宣言を行っているテンプレートを実行すると、以下のように 「パラメータ」 というフォームが表示されます
Screen Shot 2017-12-23 at 21.25.56.png

Resources

このセクションにAWSリソースを記述していきます。

雰囲気をつかむために実際にS3の作成を例に見てみましょう。

MyBucket: 
  Type: AWS::S3::Bucket 
  Properties:
    BucketName: 'MyBucket'
    LifecycleConfiguration:
      Rules:
        - Status: Enabled
          ExpirationInDays: 1

MyBucket:
1行目は論理IDの設定です。
ここは任意に命名することができ、設定したIDを元に他のリソースから参照します。

Type: AWS::S3::Bucket
2行目は作成するAWSリソースの指定です。今回はS3を作成したいので、 AWS::S3::Bucket を指定します。
他に指定できるTypeは以下を参考にしてください。
AWS リソースプロパティタイプのリファレンス - AWS CloudFormation

Properties:
この配下に含まれるのがリソースの設定値です。
このS3の設定であれば「 MyBucket というバケット名で、 ライフサイクルを1日 」となります

Outputs

主に 他のテンプレート から呼び出される、リソースが出力する値を記述するのに使います。
規模が大きくなってくると1枚テンプレートに記述しきれなくなってくるので、Outputsに他のテンプレートに渡す値を記述します。
ここについては後述します。

CloudFormationの書き方

ここからは個人的な見解が多めです。参考程度に。

ディレクトリ構成

特に制約がなければCLIから実行したいため、以下のようなディレクトリ構成にします。

.
├── apply.sh # cloudformationコマンドのキック用スクリプト
├── parameters
│   ├── develop
│   │   ├── alb.json
│   │   └── network.json
│   ├── staging
│   │   ├── alb.json
│   │   └── network.json
│   └── production
│       ├── alb.json
│       └── network.json
└── templates
    ├── alb.yaml
    └── network.yaml

apply.sh

CloudFormationはWebコンソール、CLI、SDKから実行できますが、シェルスクからCLIを実行させます。これには3つ理由があります。

  • 誰の環境でも実行できること
    • 当たり前ですがシェルスクであればSDKのようにランタイムの構築をせず動かすことが可能です。
  • パラメータを扱いやすいこと
    • CLIからはパラメータをjsonファイルで渡すことが可能なので、jsonとしてパラメータを扱うことでバージョン管理に含めることが可能になります。
    • (Webコンソールからポチポチ カタカタしたくないので...)
  • テンプレートを格納するS3の制御
    • CloudFormationはS3バケット内のファイルを指定して実行するか、ファイルを直接アップロードするかが選択できます。直接アップロードした場合ランダムな命名のバケットが作成され、そこに自動で格納されてしまいます。
    • また、複数人で作業をする際に勝手に書き換わらないようにS3バケットを用意したり、各環境毎にS3バケットを用意したりすることもできます。

parameters

これは前項でも述べましたが、パラメータをjsonファイルとして扱うことでバージョン管理に含めたいためです。
jsonファイルは以下のフォーマットで呼び出す事が可能です。

$ aws cloudformation create-stack \
    --stack-name alb-stack \
    --template-url https://s3-ap-northeast-1.amazonaws.com/<YOUR BUCKET>/templates/alb.yaml \
    --parameters file://parameters/develop/alb.json # ここでjsonファイルを呼び出している

templates

ここに各種テンプレートを格納します。

ファイル分け

テンプレートはすぐに肥大化するので、ライフサイクル毎/用途毎にテンプレートを分けます。
私であればLAMP構成の場合、templatesディレクトリ配下は以下のような形で分けます。

└── templates
    ├── alb.yaml
    ├── autoscale.yaml
    ├── network.yaml # ネットワーク系(vpc/subnet/routetable/etc)だけ1テンプレートに
    ├── rds.yaml
    └── securitygroup.yaml

そもそも管理すべきリソースかを見定める

S3やRDSやDXのような1度作成したら削除は行わないようなリソース。
LambdaやAPI GatewayのようにCloudFormationだけでは複雑過ぎるリソース。
そういったものは手作業で作成しても良いのではないのか、管理するのにもっと良いソリューション(SAMやServerless Framework)が無いかを一度考えてからコードに落とし込むことをおすすめします。

ネステッドスタックとインポートバリュー

他のテンプレートのリソースを参照したい場合にどうするか。
その場合「ネステッドスタック」というテンプレートからテンプレートを呼び出すことでファイルを擬似的に結合させる機能と、「インポートバリュー」というリソースが出力する値をグローバルに展開し受け渡す機能の2つがあります。
基本的にネステッドスタックは使わず、インポートバリューを選択します。
理由としてはネステッドスタックの制約が多いためです。
ネステッドスタックの場合、変更セットが作成できなかったり、可読性が悪かったり、理由は他にもありますが、、、最大の理由としてスタック上で管理されているリソースを手動で操作するとスタックは簡単に壊れることにあります。
ネステッドスタックの場合一箇所壊れると結合されているテンプレート全てに影響してしまい、スタック全体が壊れてしまいます。
その影響範囲を最低限に減らすためにもテンプレートはなるべく小さくし、テンプレート間の値の受け渡しにはインポートバリューを使います。

ネステッドスタック: ネストされたスタックの操作 - AWS CloudFormation
インポートバリュー: Fn::ImportValue - AWS CloudFormation

秘匿したいパラメータを扱う

テンプレート実行時にパラメータとして渡したいが、秘匿したい値を含ませたい場合が往々にしてあります
例えば、RDSを作成する際のユーザー名とパスワードや、ECSタスクの環境変数など。
こういった値をパラメータで渡すと、Webコンソールから平文で見えてしまうのが問題になってきます。
そういった平文になってしまうことを避ける方法を紹介します。

NoEcho

パラメータの宣言時に用いることで、実行後パラメータの秘匿が行えます。
これは観るのが早いでしょう。

例えばデータベースのパスワードを秘匿したいとするなら以下のようなyamlを書くことで平文で表示される回避することができます

MasterPassword: 
  NoEcho: true
  Type: String

Screen Shot 2017-12-23 at 15.33.26.png

Systems Manager パラメータ

パラメータの値に Systems Manager パラメータ(EC2パラメータストア)を用いることが可能です。
NoEchoの場合実行後に値を確認するすべがなくなるため、個人的にはこちらを併用することを推奨です。

拙作ですが、呼び出し方はこちらの記事を参照
Systems Manager パラメータをCloudFormationで使う - Qiita

再現性と汎用性

某CloudFormation勉強会で話題になった再現性と汎用性についてとりあげます。
再現性の担保はコード化をする以上もちろんですが、個人的に汎用性も担保するべきだと思っています。
どのプロジェクトでも対応可能 なテンプレートではなく、プロジェクト専用だが どの環境・どのリージョンでも対応可能 なテンプレートという意味での汎用性を担保したいです。

環境

CloudFormationを書くコストを払う以上、開発環境・ステージング・本番に対応できるだけの汎用性の対応は行いたいです
これは多少複雑度が増しますが、 Conditions セクションを用いることで実現できます。
上記の3環境だけではなくその他に、個人環境や負荷試験用環境などにも対応ができるとさらに良いですね

リージョン対応

なぜリージョン対応が必要かというと、AWSの新サービス/新機能は日本リージョンに来ていないことが往々にしてあります。
また、「同一アカウントで開発から本番環境まで扱いたい。本番環境は日本リージョンに、開発・ステージング環境は別々のリージョンに置く」といった方法で各環境を運用しているケースもあります。
そういったケースに対応するためにもリージョンの対応を行います。
(個人的に、各環境はアカウントを分けたほうが良いとは思いますが...)
CloudFormationにはリージョン情報を取得する関数が存在するので、それを用いることでハードコーディングを防ぐことができ、異なるリージョンでも素直に実行できるテンプレートを作成することができます。

例えば、サブネットの作成であれば以下のように Fn::GetAZs を用います。

MySubnet:
  Type: AWS::EC2::Subnet
  Properties:
    VpcId: 'vpc-xxxxxx'
    CidrBlock: 10.0.0.0/24
    AvailabilityZone: !Select [ 0, !GetAZs ] #=> 'ap-northeast-1a'

まとめ

CloudFormationは素晴らしいサービスですが、実際にテンプレートの記述/運用を行う場合には安くないコストを支払うことになります。
そのコストを無駄にしないためにもCloudFormationの書き方には気をつけたいですね。