CloudFormationのスタック間でリソースを参照する

AWS CloudFormation

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ども、大瀧です。 昨日Cloudformationの大規模アップデートが有り、YAMLサポートや新しい関数などと共に異なるスタックのリソース参照機能が追加されました。 今回は異なるスタックのリソース参照機能について試してみた様子をレポートします。

これまでのスタック間参照

これまでCloudFormationには、独立したCloudFormationスタック(単一のCloudFormationテンプレートから作成されたリソース一式)同士でリソースを参照する方法はなく、リソース名やリソースIDをパラメータに入力するか、テンプレートにハードコードしていました *1。 シェルスクリプトで複数のCloudFormationスタック作成を自動化するときは、aws cloudformation describe-stack-resourcesのレスポンスをパースして次のスタック作成のパラメータに代入などしていました。

スタック間参照

今回追加されたスタック間参照は、被参照テンプレートのエクスポート定義と参照するテンプレートのインポートで実現します。

エクスポート

被参照側のテンプレートでは、参照させるリソースないしアトリビュートをOutputsセクションに指定し、Export属性の下に参照時の変数名をName属性で定義します。

例えば、VPC、サブネット、セキュリティグループのIDをそれぞれSampleVPCSampleSubnetSampleSGという変数名でエクスポートします。

  "Outputs" : {
    "VPCId" : {
      "Value" :  { "Ref" : "VPC" },
      "Export" : { "Name" : "SampleVPC" }
    },
    "PublicSubnet" : {
      "Value" :  { "Ref" : "PublicSubnet" },
      "Export" : { "Name" : "SampleSubnet" }
    },
    "WebServerSecurityGroup" : {
      "Value" :  { "Fn::GetAtt" : ["WebServerSecurityGroup", "GroupId"] },
      "Export" : { "Name" : "SampleSG" }
    }
  }

こんな感じです。

インポート

エクスポートした変数を参照するテンプレートでは、組み込み関数Fn::ImportValueで変数名を指定します。Refと同じような感覚で利用できます。

  "Resources" : {
    "WebServerInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "InstanceType"   : "t2.micro",
        "NetworkInterfaces" : [{
          "SubnetId" : { "Fn::ImportValue" : "SampleSubnet" },
          "GroupSet" : [ { "Fn::ImportValue" : "SampleSG" } } ],
            :(略)

結構お手軽ですね。

制約と対策

上の例を見て気づいた方もいるかも知れませんが、何も考えずに変数を定義するとテンプレートにハードコードすることになります。つまり、同一テンプレートから複数のスタックを作成しようとすると、変数名の重複でエラーになります。CloudformationでCloud Design Patternのスタンプパターンのようなことをやろうとするとハマるので注意が必要です。

変数名を可変にするために、以下の方法を考えてみました。

エクスポートの改良

文字列決め打ちではなく、Refによる参照もしくは新しく追加された文字列代入のFn::Subを利用します。今回はスタック名と文字列の組み合わせにしてみました。

  "Outputs" : {
    "VPCId" : {
      "Value" :  { "Ref" : "VPC" },
      "Export" : { "Name" : { "Fn::Sub" : "${AWS::StackName}-VPC" } }
    },
    "PublicSubnet" : {
      "Value" :  { "Ref" : "PublicSubnet" },
      "Export" : { "Name" : { "Fn::Sub" : "${AWS::StackName}-Subnet" } }
    },
    "WebServerSecurityGroup" : {
      "Value" :  { "Fn::GetAtt" : ["WebServerSecurityGroup", "GroupId"] },
      "Export" : { "Name" : { "Fn::Sub" : "${AWS::StackName}-SecGroup" } }
    }
  }

なおFn::Subと似た、組み込み関数のFn::Joinはエラーで使えませんでした。戻り値が純粋な文字列では無いためのようです。

インポートの改良

インポート側も同様に可変になるような仕掛けを考えました。エクスポートで試したFn::SubFn::JoinはエラーになってしまいましたがRefが通ったので、Parametersを経由してRefで参照させることで可変にすることができました。

  "Parameters": {
    "NetworkStackSubnet": {
      "Type": "String",
      "Default" : "SampleNetworkCrossStack-Subnet"
    },
    "NetworkStackSecGroup": {
      "Type": "String",
      "Default" : "SampleNetworkCrossStack-SecGroup"
    }
  },
  "Resources" : {
    "WebServerInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "InstanceType"   : "t2.micro",
        "NetworkInterfaces" : [{
          "SubnetId" : { "Fn::ImportValue" : { "Ref" : "NetworkStackSubnet" } },
          "GroupSet" : [ { "Fn::ImportValue" : { "Ref" : "NetworkStackSecGroup" } } ],
            :(略)

これで、複数スタックを作ったときも重複せずにそれぞれのスタック名から変数名を指定し、参照することができます。

まとめ

CloudFormationのスタック間でリソースを参照する方法について解説しました。若干クセのある機能ですが、上手く活用してCloudFormationによるAWS環境構築に役立ててください!

なお、現時点ではエクスポートした変数一覧を確認する機能が見当たらないので、どんな変数が定義されているのかは、作成したスタックの[Template]タブからテンプレートのOutputsセクションを片っ端から確認していくしかなさそうです。。。

参考URL

脚注

  1. テンプレートのリソースに別のテンプレートを指定する、いわゆるテンプレートの親子関係はこの限りではなく、子テンプレートから親テンプレートのリソースを参照することが可能です。

AWS Cloud Roadshow 2017 福岡