AWS CloudFormationのネストしたスタックのテンプレートを作ってみた

追記: http://keiwt.hatenablog.com/entry/2016/09/22/173231

実際CloudFormationを使用するにあたってAWSのサンプルテンプレートがありましたが、ネストしたスタックのいいテンプレートがまだないので、自分でテンプレートを作ってみることにしました。

作成したのがこちら
github.com

ついでにCloudFormationベストプラクティスを見てみます。

ライフサイクルと所有権によるスタックの整理

通常、すべてのリソースは 1 つのスタックに置かれますが、スタックの規模が大きくなり拡張する必要が出てきた場合は、単一のスタックでの管理は手間と時間がかかります。共通のライフサイクルと所有権を持つリソースのグループ化により、所有者は独自のプロセスやスケジュールを使用して、他のリソースに影響を与えることなくリソースのセットを変更できます。

これを実現するには以下のようにして、他のスタックのものを使用します。

  • VPCスタックを親スタックでリソースとして定義
  "Resources": {
    "VPCStack": {
      "Type": "AWS::CloudFormation::Stack",
      "Properties": {
        "TemplateURL"     : { "Fn::Join": ["", [
          { "Fn::FindInMap": [ "CloudFormationSettings", "S3", "TemplateDir"]},
          "VPC.template"
        ]]},
        "TimeoutInMinutes": "5"
      }
    },
  }
  ........
  • VPCスタック内では参照をOutputsとして親スタックに渡す
  "Resources": {
    "VPC": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock"         : { "Ref": "VPCCidrBlock" },
        "InstanceTenancy"   : "default",
        "EnableDnsSupport"  : "true",
        "EnableDnsHostnames": "true",
        "Tags": [
          { "Key": "Name", "Value": { "Ref": "AWS::StackName" } }
        ]
      }
    },
    ......
  },
  "Outputs" : {
    "VPC" : {
      "Value" : { "Ref" : "VPC" }
    }
  }
  • 親スタックから他のスタックにOutputsをパラメーターとして渡す
  "Resources": {
    .....
    "EC2Stack": {
      "Type": "AWS::CloudFormation::Stack",
      "Properties": {
        "TemplateURL"     : { "Fn::Join": ["", [
          { "Fn::FindInMap": [ "CloudFormationSettings", "S3", "TemplateDir"]},
          "EC2.template"
        ]]},
        "TimeoutInMinutes": "5",
        "Parameters"      : {
          "VPC": { "Fn::GetAtt" : [ "VPCStack", "Outputs.VPC" ] }
        }
      }
    }
  }
  • 子スタックでは渡されたパラメーターを使用する
  "Parameters" : {
    "VPC" : {
      "Type" : "String"
    },
    ..........
  },
  "Resources": {
    "SGEC2Web": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Allow http to client host",
        "VpcId": { "Ref" : "VPC" },
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort"  : "80",
            "ToPort"    : "80",
            "CidrIp"    : "0.0.0.0/0"
          }
        ],
        "SecurityGroupEgress": [
          {
            "IpProtocol": "tcp",
            "FromPort"  : "80",
            "ToPort"    : "80",
            "CidrIp"    : "0.0.0.0/0"
          }
        ],
        "Tags": [
          { "Key": "Name", "Value": { "Ref": "AWS::StackName" } }
        ]
      }
    }
  }

これで親スタックと子スタックでパラメーターの受け渡しができるようになるので、あとはスタックを分けるだけです。

また、スタックを分けない場合はいずれ51200バイトを超えたとのエラーが発生して、スタックを分けることになります。
スタックを分けた場合はリソースも再作成されることになります。

スタックの上限

AWS アカウントのリージョンごとに起動できる AWS CloudFormation スタックは 200 のみです。

テンプレートを再利用して複数の環境にスタックを複製する

  • EnvTypeの設定
  "Parameters": {
    "EnvType": {
      "Description"  : "Environment type.",
      "Type"         : "String",
      "Default"      : "test",
      "AllowedValues": ["prod", "test"],
      "ConstraintDescription": "must specify prod or test."
    }
  },
  "Conditions": {
    "CreateProdResources": {"Fn::Equals": [{ "Ref": "EnvType" }, "prod"]}
  },
  "Resources": {
  ....
  }

※EnvTypeはスタック作成時に選択できるようになっています。
コマンドラインの場合はコマンド実行時に指定します。
参照

テンプレートに認証情報を埋め込まない

  • 親テンプレートでパラメーターとしてスタック作成時に入力するようにして、NoEcho: "true"にする
"Parameters" : {
  "DBPwd" : {
    "NoEcho" : "true",
    "Description" : "The database admin account password",
    "Type" : "String",
    "MinLength" : "8",
    "MaxLength" : "41",
    "AllowedPattern" : "[a-zA-Z0-9]*"
  }
}

参照
※注意点としては子スタックにパラメーターとして渡す場合は子スタックでも、NoEcho: "true"にすることです

テンプレートを使用する前に検証する

コマンドは以下の通りです

aws cloudformation validate-template --template-body file:////Users/hoge/projects/cloud-formation/aws.template
aws cloudformation validate-template --template-url https://s3-ap-northeast-1.amazonaws.com/cf-templates-xxxxxxxxxxxx-ap-northeast-1/stacks/aws.template

参考

AWS CloudFormation ですべてのスタックリソースを管理する

AWS CloudFormation 以外のスタックリソースは変更しないでください。変更するとスタックのテンプレートとスタックリソースの現在の状態の間で不一致が起こり、スタックの更新または削除でエラーが発生する場合があります。 ※スタックとリソースの不一致が生まれるコンソールでの作業はしない

スタックポリシーを使用する

{
  "Statement" : [
    {
      "Effect" : "Deny",
      "Action" : "Update:*",
      "Principal": "*",
      "Resource" : "LogicalResourceId/ProductionDatabase"
    },
    {
      "Effect" : "Allow",
      "Action" : "Update:*",
      "Principal": "*",
      "Resource" : "*"
    }
  ]
}

参照

コードの確認とリビジョン管理を使用してテンプレートを管理する

GitHubでソースと同様にインフラもリビジョン管理する

上記に加えて、削除時には手放したくないもの(EIPやS3のバケットなど)は以下のようにしてスタック削除時に削除されないようにします。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Resources" : {
    "myS3Bucket" : {
      "Type" : "AWS::S3::Bucket",
      "DeletionPolicy" : "Retain"
    }
  }
}

参考