LoginSignup
2
3

More than 5 years have passed since last update.

CloudFormation の設定用JSONでもう少し幸せになる方法

Posted at

前回、AWS の設定用JSONで少し幸せになる方法 ってことで書いたネタの続き。
いろいろいじってると、もう少し便利にしたくなってきました。

設定のコメントアウト

1個目はシンプルなもの。
CloudFormation のデバッグをしていると、いろいろエラーが出てきて、コメントアウトしたい!ってなるんだけど、前回の正規表現だと、意識的にコメントをつけることは出来ても、JSONとして書いているところをコメントアウト出来ない。

ということで、下記のようにコメントアウトにも対応しました。先頭に 「//」がある時しか対応してないけど、個人的にはこれだけで十分です。

    def self.strip_comments(str)
        ret_str = str.gsub(/\/\/[^'"]*$/, "\n").gsub(/^\/\/*$/, "")
        return ret_str
    end

CloudFormation のファイル分割

JSONファイルが長くなるとやっぱりわけたい。
もともと CloudFormation には分割のための TemplateUrl とかあるんですが、手元のファイルで分割したりしながらやりたいわけで…

でも、分割しちゃうと、いろいろな IDの参照とかが出来なくなってしまうので、いちいち IDを確認して Parameters に書き入れるなんて作業が必要になり、作っては消し するたびにむなしいコピペが発生します。

Terraform 導入も考えたけど、まだいろいろツラみもあるようだし…
ファイル分割できるだけで だいぶ幸せになれるんだけどな〜 と思って、少し Ruby SDK を眺めたところ、それなりに便利な方法が出来ました :-)

前提

  • VPC で切られた環境に構築。
  • CloudFormation の設定ファイルは、例えば下記みたいに分割
    • ネットワーク設定
    • 踏み台サーバ
    • ELB
  • CloudFormation の各設定は、自分で依存関係を意識した上で、それぞれ順番に別stackとして実行する。
  • 分割された個々のファイルの中で、参照したい {"Ref": } については、ちゃんと "Parameters" の中で定義しておく(値は後で上書かれる)。

分割した設定ファイルをそれぞれ分割して別stack として実行するので、踏み台サーバの stack だけを削除して、デバッグして再実行する なんてことも出来ます。

もちろん、設定ファイルは、もっと増やしてもOKです。

Parameters の解決方法

AWS SDK for Ruby を入れている状態で、下記みたいにやってみると...

[ec2-user@ip-xx-xxx-xx-xx scripts]$ aws.rb --region ap-northeast-1
Aws> cf = Aws::CloudFormation::Client.new
=> #<Aws::CloudFormation::Client>
Aws> cf.describe_stack_resources({ :stack_name => "sample" })

いろいろなリソースが出てくるんですが、すべてのリソースについて下記のような記載が出てきます。

logical_resource_id="AttacheGateway",
physical_resource_id="VPC-f-Attac-xxxxxxxxxxxxx",
resource_type="AWS::EC2::VPCGatewayAttachment",

どうやら logical_resource_id というのが CloudFormation で書いた定義名で、physical_resource_id というのが、参照時に入れるべき値のようです。
とすると、これらの値を取得して Parameters につっこんであげれば、参照の問題が解決しそう。

ファイル分割の実行例

だいぶ省略して雑(&せつな)に書いてますが、こんな感じの ruby スクリプトを用意しました。

aws-cf-script.rb
#! /usr/bin/ruby
require './aws-common.rb'

stacks = {
    "network" => {
        :template => "./json/network.json",
        :ref => []
    },
    "fumidai" => {
        :template => "./json/fumidai.json",
        :ref => ["network"]
    },
    "elb" => {
        :template => "./json/elb.json",
        :ref => ["network"]
    }
}

vpc_name = ARGV[0]
template = ARGV[1]
stack_name = vpc_name + "-" + template

params = AWSutil.load_params_from_stack(vpc_name + "-" + stacks[template][:ref][0])
params = AWSutil.delete_unused_params(stacks[template][:template], params)
AWSutil.cf_start(stack_name, stacks[template][:template], params)
aws-common.rb
require 'aws-sdk'
require 'aws_config'
require './jsoncfg.rb'

Aws.config.update(AWSConfig.default.config_hash)

module AWSutil
    def self.cf_start(stack_name, template_file, params)
        cf = Aws::CloudFormation::Client.new

        template = JSON_cfg.loadFromFile(template_file)
        template_body = JSON.generate(template)
        puts JSON.pretty_generate(template)

        stack = Aws::CloudFormation::Stack.new(stack_name)

        if stack.exists? then
            cf.update_stack({ stack_name: stack_name, template_body: template_body, on_failure: "DO_NOTHING", capabilities: ["CAPABILITY_IAM"], parameters: params })
        else
            cf.create_stack({ stack_name: stack_name, template_body: template_body, on_failure: "DO_NOTHING", capabilities: ["CAPABILITY_IAM"], parameters: params })
        end
    end

    def self.delete_unused_params(template_file, params)
        template = JSON_cfg.loadFromFile(template_file)
        t_params = template["Parameters"]
        new_params = []
        params.each do |param|
            if t_params[param["parameter_key"]]
                p param["parameter_key"] + ": " + param["parameter_value"]
                new_params.push param
            else
            end
        end
        return new_params
    end

    def self.load_params_from_stack(stack_name)
        cf = Aws::CloudFormation::Client.new
        resources = cf.describe_stack_resources({ :stack_name => stack_name })

        params = []
        resources[0].each do | res |
            param = {}
            param["parameter_key"] = res[:logical_resource_id]
            param["parameter_value"] = res[:physical_resource_id]
            param["use_previous_value"] = true
            params.push(param)
        end

        return params
    end
end

やっていることはシンプルで、下記のとおりです。

  1. 実行するstackの中で、参照したいリソースに関するstack を定義する
  2. 参照先の stack に関する describe_stack_resources を呼ぶ
  3. describe_stack_resources の返り値をもとに params を作る
  4. 実行したいテンプレートに存在しない params を削除する
  5. CloudFormation 実行時の Parameters として取得した params を設定したうえでテンプレートを実行する
実行例
% ruby ./aws-cf-script.rb sample elb

こうすると、elb が参照している "sample-network" stack のリソースを取得し、VPCのIDなどを parameters として取り込んで、"sample-elb" stack を実行してくれます。

こんな感じで、ファイルも分割できて、コメントもつけられるようになり、CloudFormation の設定用JSON を作る作業がさらに少し幸せになりました :-)

2
3
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
2
3