CloudFormationはガチ

記事タイトルとURLをコピーする

こんにちは、開発の千葉です。

今回は、CloudFormationをガッツリ使ってみました。
ハマリどころ満載でしたので、利用を検討されている方の参考になればと思い投稿する次第です。

実案件の構成は公開することができないので
本稿では、以下の構成をCloudFormationで実現することを目的とします。

実利用の際は、必要なパッケージがインストール済みのAMIを用意して
それをCloudFormationから起動するのがセオリーなのですが
今回はAmazonLinuxのAMIに、それらのパッケージをインストールする作業もCloudFormationから実現してみます。

作業の流れは、AWS CloudFormationを一読したあとに
CloudFormerからテンプレートを自動生成します。

ここで生成されたテンプレートは、そのままでは使えたモノではないので Template Referenceから
リソースの起動/動的パラメータの指定/レスポンスの取得の記載方法を調べしらべテンプレートを編集します。

参考までに、この構成から作成したテンプレートファイルを貼っておきます。

さて、ここからは実案件で利用した際に、つまづいたポイントをお伝えしたいと思います。

CloudFormerから出力結果について

先程も触れましたが、CloudFormerから出力された内容はアテになりません。
基本的には不要な設定値を削除したり、動的な値に書き換えたりすることで目的のテンプレートを作成するのですが
CloudFrontの出力結果に、一部不備がありました。

"FooCloudFront": {
  "Type": "AWS::CloudFront::Distribution",
  "Properties":
    "Enabled": true,
    "CNAMEs": [
      "www.example.com"
    ],
    "DistributionConfig": {
      "CustomOrigin": {
        "DNSName": "example-LoadBalancer-xxxxxxxxxxxx-yyyyyyyyyy.ap-northeast-1.elb.amazonaws.com",
        "OriginProtocolPolicy": "http-only",
        "HTTPPort": "80",
        "HTTPSPort": "443"
      }
    }
  }
}

こちらの『"Enabled": true』は『Properties』以下ではなく、『DistributionConfig』以下に記載する必要があります。

"FooCloudFront": {
  "Type": "AWS::CloudFront::Distribution",
  "Properties":
    "CNAMEs": [
      "www.example.com"
    ],
    "DistributionConfig": {
      "Enabled": true,
      "CustomOrigin": {
        "DNSName": "example-LoadBalancer-xxxxxxxxxxxx-yyyyyyyyyy.ap-northeast-1.elb.amazonaws.com",
        "OriginProtocolPolicy": "http-only",
        "HTTPPort": "80",
        "HTTPSPort": "443"
      }
    }
  }
}

S3のBucket指定について

『{ "Ref" : "S3Log" }』と記載することで『xxxx.s3.amazonaws.com』を期待し
CloudFrontのログ出力バケットを指定する際に定義したのですが

"Logging" : {
  "Bucket" : { "Ref" : "S3Log" },
  "Prefix" : ""
}

これでは、なぜか失敗してしまいました。
回避策としては、以下のとおりドメイン名以降を記載しました。

"Logging" : {
  "Bucket" : { "Fn::Join" : ["", [{ "Ref": "S3Log" }, ".s3.amazonaws.com"]] },
  "Prefix" : ""
}

CloudInitの利用について

CloudInitを利用して、起動時に設定ファイル等を編集する必要があると思います。
以下の例では、パラメータで入力されたFrontFQDNの値『www.example.com』を、
/tmp/foo.pmファイルの1001行目に
半角スペース2個のインデント付きので『$foo_url = "http://www.example.com/";』として追記している例です。

"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [
  "sed -i "1000a   $preview_url =~ s/http:\/\/", { "Ref" : "FrontFQDN" }, "//;" /tmp/foo.pmn"
] ] } }

もう、エラいこっちゃです。

説明が面倒なので、ここではデバッグのススメをだけお伝えしたいと思います。
CloudInitで実際に実行されるコマンドは、起動されたインスタンスの
/var/lib/cloud/data/user-data.txtにシェルスクリプトとして出力されていますので
意図したとおりに動かない場合は、こちらをご確認してください。

実行時間について

実案件にて、自分が作成した構成ではCloudFront,RDS,S3などなど沢山のサービスを利用しました。
特にCloudFrontとRDSは起動に時間がかかる為、毎回15分程度の待ちになってしまいNGだった際の精神的なダメージがキツかったです。
これについては、例えばEC2+ELB、DBSecurity+RDSの様に、構成を適当なパーツ毎に区切り
それぞれが、正しく動作することを確認した後に統合することをおすすめします。

ひとつの案件でテンプレートを作ってみて

JSONで記載できるので、取り掛かりは容易です。
ただし、意図したとおりに動くまでの作業は非常に泥臭いです。

理想は、環境構築の手順が複雑な案件については
手順書を書くのではなく、ダレかがテンプレートを書くべきだと思いました。
(おれは書きたくないな。。。)