katsuyukikun’s diary

とある天パーエンジニアのblog

awsサービス cloudformationが想像以上に良い!!

以前サーバー移行の話をちらっとだけ書きました。

 

katsuyukikun.hatenablog.com

 

デプロイ方式はBlueGreenでやっています。

この時、環境構築するのにcloudformationを使用していました。

 

aws.amazon.com

 

テンプレートに記述するだけで新規・既存問わずに自由に設定できます。

僕の時は「既存のVPCの中の既存のセキュリティグループにインスタンス2台(web機とapplication機)を構築する」という感じで作っていました。

ロードバランサーにALBを使いたかったので、2台立てることになりました。

ALBはpublicにしかアタッチできないとか聞いてなかったです笑

 

   本当はこうしたかった                                         でもこうなった 

f:id:katsuyukikun:20170517233807p:plain              f:id:katsuyukikun:20170517234004p:plain

 

publicにweb機(nginx)、privateにapplication機(unicorn)をおくことにしました。で、この時cloudformationでここの関係性も定義しました。こういうのを用意しておいてくれるのは本当に助かりました。

 

良かったところ①

 

インスタンス構築時にこういうプロパティ宣言ができるようになっています。

 

"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
  "#! /bin/bash -v\n",
  "yum update -y\n",
  "yum install -y nginx\n"
]]}}

 

なのでnginxインストール済みのインスタンス作成できちゃいます。

docs.aws.amazon.com

 

 

しかもこれ結構よくてnginxの/etc/nginx/test.confファイルにこう書いておく

 

location / {
 proxy_pass http://private_ip_address:8080;
}

 

cloudformatationのnginxのインスタンスUserDataにこう書く

 "sudo sed -i \"s/private_ip_address/",
{
    "Fn::GetAtt": [
        "Ec2Instance",
        "PrivateIp"
    ]
},
"/g\" /etc/nginx/test.conf \n",
]]}}

 

 

そうするとnginxインストール済みインスタンスunicornを設定済みインスタンスの繋ぎこみ完了している状態で作成されます笑。これできた時は少し感動でした。

 

良かったところ②

lambdaが使えるところww

インスタンス立ち上げ終わったあとに、lambdaキックさせてターゲットグループの入れ替えというのをやりたかったんですけど、この辺もカバーしてくれて助かりました笑。

 

それを実現するためにカスタムリソースってのが用意されてます。

docs.aws.amazon.com

 

 

 
"CustomResource" : {
  "Type" : "AWS::CloudFormation::CustomResource",
    "Properties" : {
        "ServiceToken": "lambdaのARN",
        "Test1": {"Ref": "InstanceName"},
        "Test2": {"Ref": "TargetGroupName"}
    }
}

ServiceTokenってのにlambdaのARN入れるだけで構築し終わった後そのlambdaを叩きにいってくれます。しかもTest1といった任意の文字列もeventの一部として渡せます!便利なんですけど、ここで詰まったポイントがあります。

詰まりポイント

lambdaにある記述を書かないとcloudformationのタスクがプログレスのまま終わらない・・・そして一時間後くらいにfailedとなってしまう。

なんか cfn-response モジュールなるものが必要らしいです。

 

docs.aws.amazon.com

 

 

cfn-response モジュールは、ZipFile プロパティを使用してソースコードを作成した場合にのみ使用できます。S3 バケットに保存されたソースコードには使用できません。S3 バケットのコードでは、独自の関数を作成してレスポンスを送信する必要があります。

 

 

とあります。これURL見てもらえらば良いのですが、 

 

"ZipFile": { "Fn::Join": ["", [
  "var response = require('cfn-response');",
  "exports.handler = function(event, context) {",
  "  var input = parseInt(event.ResourceProperties.Input);",
  "  var responseData = {Value: input * 5};",
  "  response.send(event, context, response.SUCCESS, responseData);",
  "};"

 上記のようにcloudformationの中に直接コードを書きましょということです。 

・・・・・いや、さすがにそれはいやだよ?w

ってことでマネジメントコンソールの方に記述したいからこの方法とったのに・・・と思ったんですがgithubみるとこんな感じ。

 

github.com

 

結局、cloudformation側にレスポンス返せばいいだけだったので自分で実装してしおうと思いました。(まあコピペですな)lambda側に以下を書いてあげれば良い。

 


var SUCCESS = "SUCCESS";
var FAILED = "FAILED";
 
var cfnResponse = function(event, context, responseStatus, responseData, physicalResourceId) {
 
    var responseBody = JSON.stringify({
        Status: responseStatus,
        Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
        PhysicalResourceId: physicalResourceId || context.logStreamName,
        StackId: event.StackId,
        RequestId: event.RequestId,
        LogicalResourceId: event.LogicalResourceId,
        Data: responseData
    });
 
    console.log("Response body:\n", responseBody);
 
    var https = require("https");
    var url = require("url");
 
    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
        hostname: parsedUrl.hostname,
        port: 443,
        path: parsedUrl.path,
        method: "PUT",
        headers: {
            "content-type": "",
            "content-length": responseBody.length
        }
    };
 
    var request = https.request(options, function(response) {
        console.log("Status code: " + response.statusCode);
        console.log("Status message: " + response.statusMessage);
        context.done();
    });
 
    request.on("error", function(error) {
        console.log("send(..) failed executing https.request(..): " + error);
        context.done();
    });
 
    request.write(responseBody);
    request.end();
}

exports.handler = (event, context, callback) => {
    // TODO implement
    if(event['RequestType'] == 'Delete'){
        cfnResponse(event, context, SUCCESS, {});
    }
    console.log(event);
    cfnResponse(event, context, SUCCESS, {});
};

 

 これで無事lambdaにキックさせ、かつタスクもちゃんと終わってくれます。

 

 

まとめ

さすがAWSって感じですね!

ちゃんと手を届かせてくれるあたりがユーザーとしては満足度高いです。

もっと知ればいろんなことができそうなので他のサービス含め触り倒していきたいなって思います。

 

 

 

あっGoogle I/O見ないと・・・・

 

 

終わり。