2014-03-02

ApexからAutoScaling触ってみる。

今回はAuto ScalingをApexからREST APIで触ってみます。

 

Auto Scalingは指定したインスタンスが落ちたら自動的に指定のインスタンス数になるまで

同じインスタンスを立ててくれたり、指定した時刻にインスタンスを動的に増減させることのできる

自動でスケーリングをしてくれるサービスです。(って名前そのまんまの説明…)

 

これを使うと、

「毎週日曜日は負荷が高いから自動的に処理をするインスタンス数を増やそう!」とか

「万が一インスタンスが落ちてもすぐにインスタンスを立ち上げるようにしよう!」とか

「不健全なインスタンスを落として健全なインスタンスだけ生かしたい!」とか

こういったスケールアウト、スケールインが自由自在にできちゃいます。

 

前回はEC2をREST APIを使って手動で立ち上げていて、

”EC2のREST APIを使ってスケジュール化するためにはApexバッチが必要”と書きましたが、

このAuto Scalingを使えばスケールアウトを自動化できるので

インスタンス数を0→1にスケールアウトするようなスケジューリングをAuto ScalingをCUIとかで

一回設定してあげれば自動インスタンス起動の処理ができちゃいます。

 

Auto Scalingのスケジューリングに関する参考URL↓

http://dev.classmethod.jp/cloud/auto-scaling-schedule/

http://blog.suz-lab.com/2012/04/ec2.html

 

また、Auto Scalingを利用しない単純なEC2インスタンス起動は、立ち上げた後は負荷が高かろうが

インスタンスが落ちようが、別のAPIでチェックしない限りは検知することができないのと

検知しても自動的にスケールさせたり、落ちた時に自動的にインスタンスを立ち上げるという

仕組みを自前で実装しなければなりません。

 

そこで、Auto Scalingの出番ということです!

 

ということで、今回のサンプル!↓

public with sharing class AutoScalingCommon {
    /**
     * AccessKey
     */
    public String AWSAccessKeyId = EnvSetting__c.getOrgDefaults().AWSAccessKeyId__c;

    /**
     * SecretKey
     */
    public String AWSSecretKeyId = EnvSetting__c.getOrgDefaults().AWSAccessKeySecret__c;

    private final String REGION = 'us-east-1';

    private final String SERVICE = 'autoscaling';

    /**
     * AutoScalingGroupの更新(インスタンス数の最大、最小を変更)
     */
    public String updateAutoScalingGroup(
        String autoScalingGroupName,
        Integer minSize,
        Integer maxSize
    ) {
        List<String> signedHeaders = new List<String>{
            'host', 
            'x-amz-date',
            'content-length'
        };

        DateTime dt = DateTime.now();
        String credentialScope = dt.formatGmt('YYYYMMdd') + '/' + 
                                 REGION + '/' + SERVICE + '/aws4_request';

        Map<String, String> params = new Map<String, String>{
            'Action' => 'UpdateAutoScalingGroup',
            'Version' => '2011-01-01',
            'AutoScalingGroupName' => autoScalingGroupName,
            'MinSize' => String.valueOf(minSize),
            'MaxSize' => String.valueOf(maxSize)
        };
        String body = Utility.getParam(params);

        String stringToSign = this.createStringToSign(
            'AWS4-HMAC-SHA256',
            dt, 
            credentialScope, 
            this.createHashedCanonicalRequest(
                'SHA256',
                'POST',
                '/',
                new Map<String, String>{},
                new Map<String, String>{
                    'Host' => 'autoscaling.' + region + '.amazonaws.com',
                    'x-amz-date' => dt.formatGmt('YYYYMMdd') + 'T' + 
                                    dt.formatGmt('HHmmss') + 'Z',
                    'content-length' => String.valueOf(body.length())
                },
                signedHeaders,
                body
            )
        );

        String signature = this.createSignature(
            'hmacSHA256',
            dt,
            region,
            SERVICE,
            Blob.valueOf(stringToSign)
        );

        return this.callAutoScalingCallCommon(
            dt,
            'autoscaling.' + REGION + '.amazonaws.com',
            credentialScope,
            signedHeaders,
            signature,
            body
        );
    }

    /**
     * AutoScalingへのAPIコールの共通メソッド
     */
    private String callAutoScalingCallCommon(
        DateTime dt,
        String hostname,
        String credentialScope,
        List<String> signedHeaders,
        String signature,
        String body
    ) {
        HttpRequest req = new HttpRequest();
        req.setHeader('Host', hostname);
        req.setHeader(
            'x-amz-date', 
            dt.formatGmt('YYYYMMdd') + 'T' + dt.formatGmt('HHmmss') + 'Z'
        );

        req.setHeader('Content-Length', String.valueOf(body.length()));

        req.setHeader(
            'Authorization',
            'AWS4-HMAC-SHA256 Credential=' + this.AWSAccessKeyId + '/' + credentialScope + ',' +
            'SignedHeaders=' + String.join(Utility.getLowerCaseSortedList(signedHeaders), ';') + ',' + 
            'Signature=' + signature
        );
        req.setEndpoint('https://' + hostname + '/');
        req.setMethod('POST');

        req.setBody(body);

        Http http = new Http();
        HTTPResponse res = http.send(req);
        return res.getBody();
    }

    /**
     * HashedCanonicalRequestを作成
     */
    private String createHashedCanonicalRequest(
        String algorithm,
        String method,
        String url,
        Map<String, String> params,
        Map<String, String> headers,
        List<String> signedHeaders,
        String payload
    ) {     
        String signature = '';
        signature += method + Constants.LF;
        signature += url + Constants.LF;
        signature += Utility.getSortedParam(params) + Constants.LF;
        signature += Utility.createCanonicalHeaders(headers) + Constants.LF;
        signature += String.join(Utility.getLowerCaseSortedList(signedHeaders), ';') + Constants.LF;
        signature += EncodingUtil.convertToHex(
                        Crypto.generateDigest(
                            algorithm,
                            Blob.valueOf(payload)
                        )
                     );
        return EncodingUtil.convertToHex(
            Crypto.generateDigest(
                algorithm, 
                Blob.valueOf(signature)
            )
        ).toLowerCase();
    }

    /**
     * StringToSignを作成
     */
    private String createStringToSign(
        String algorithm,
        DateTime requestDateTime,
        String credentialScope,
        String hashedCanonicalRequest
    ) {
        String stringToSign = algorithm + Constants.LF;
        stringToSign += 
           requestDateTime.formatGmt('YYYYMMdd') + 'T' + 
           requestDateTime.formatGmt('HHmmss') + 'Z' + Constants.LF;
        stringToSign += credentialScope + Constants.LF;
        stringToSign += hashedCanonicalRequest;
        return stringToSign;        
    }

    private String createSignature(
        String algorithm,
        DateTime requestDateTime,
        String region,
        String service,
        Blob stringToSign
    ) {
        Blob kDate = Crypto.generateMac(
            algorithm,
            Blob.valueOf(requestDateTime.formatGmt('YYYYMMdd')),
            Blob.valueOf('AWS4' + this.AWSSecretKeyId)
        );
        Blob kRegion = Crypto.generateMac(
            algorithm,
            Blob.valueOf(region),
            kDate
        );
        Blob kService = Crypto.generateMac(
            algorithm,
            Blob.valueOf(service),
            kRegion
        );

        Blob kSigning = Crypto.generateMac(
            algorithm,
            Blob.valueOf('aws4_request'),
            kService
        );

        return EncodingUtil.convertToHex(
            Crypto.generateMac(
                algorithm,
                stringToSign,
                kSigning
            )
        );
    }
}

Auto ScalingはSignature Version2Version4の両方が使えるんですが、

Version4を推奨、とリファレンスに書いてあったので素直に4でやってます。

Signature Version4はDynamoDBのときにやっていたので、大枠はその回のコピペです。

 

っていうかやっぱりVersion4は複雑…。

ちゃんと間違っていたらResponseで答えを教えてくれるんで、正解にはたどり着きやすいんですけどねー。

(ちなみにSWFはResponseで答え教えてくれないっていう)

 

メソッドはupdateAutoScalingGroupしか無いですが、

SalesforceからコールするAPIとしてはこれで十分かと思われます。

 

基本的にはconsole GUIやCUIであらかじめ作成・スケジュール設定をした

launch configulationやauto scaling groupに対して

Salesforceはauto scaling groupの更新でインスタンスの増減をコントロールする運用を想定していて、

ワンタイムの設定のためにSalesforceでわざわざAPIを叩くのはあまり意味が無い気がするからです。

このエントリーをはてなブックマークに追加