AWS Mobile Hubでサーバ管理をサーバレスにした話

# 概要
AWS Mobile Hubを使って社内にある[AMIMOTOマネージドホスティング](https://ja.amimoto-ami.com/plans/hosting/)のサーバをiPhoneアプリで一元管理するようにしました。

# 構成図
![Untitled (2).png](https://qiita-image-store.s3.amazonaws.com/0/65478/53a002b4-7a4f-4dc8-535d-a6382438e8b9.png)
モバイルアプリからの指令でインスタンスの起動と停止とインスタンスサイズの変更が可能になってます。それらはすべてLambdaファンクションで処理させます。

CloudWatchのアラートが上がったらSNS経由でアプリにpush通知を送ります。

# Cloud Logic
<img width="838" alt="スクリーンショット 2015-11-27 0.17.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/a2d7d32f-5d21-15b2-e361-624c40354e3d.png">
Mobile Hub上で動くLambdaファンクションを管理してくれます。これチェックが付いているLambdaファンクションのみがアプリからのリクエストを受け取れるようになってます。

実際のLambdaファンクションは以下のような感じ

```js:start-instance
var AWS = require('aws-sdk');

function startInstance(instance_id, context) {
    var ec2 = new AWS.EC2();
    var params = {
        InstanceIds: [
            instance_id
        ]
    };
 
    ec2.startInstances(params, function(err, data) {
        if (err){ console.log(err, err.stack);
        } else {
            ec2.waitFor('instanceRunning', params, function(err, data) {
                if (err) console.log(err, err.stack);
                else     context.done(null, data); 
            });
        }
    });
}

exports.handler = function(event, context) {
    startInstance(event.instance_id, context);
};
```

```js:get-metric-cpu
var AWS = require('aws-sdk');

function ISODateString(d){
  function pad(n){return n<10 ? '0'+n : n}
  return d.getUTCFullYear()+'-'
    + pad(d.getUTCMonth()+1)+'-'
    + pad(d.getUTCDate())+'T'
    + pad(d.getUTCHours())+':'
    + pad(d.getUTCMinutes())+':'
    + pad(d.getUTCSeconds())+'Z'
}
function getTimeStamp(d){
  var now = ISODateString(d);
  return now;
}

function getMetricStatics(event, context) {
    var cloudwatch = new AWS.CloudWatch();
    var past       = new Date();
    past           = new Date(past.setHours(past.getHours() - 1));
    var now        = new Date();
    var endtime    = getTimeStamp(now);
    var starttime  = getTimeStamp(past);
    var params = {
                    EndTime: endtime,
                    MetricName: 'CPUUtilization',
                    Namespace: 'AWS/EC2',
                    Period: 300,
                    StartTime: starttime,
                    Statistics: [ 
                        'Average'
                    ],
                    Dimensions: [
                        {
                            Name: 'InstanceId',
                            Value: event.instance_id
                        }
                    ]
    };

    cloudwatch.getMetricStatistics(params, function(err, data) {
                    if (err) console.log(err, err.stack);
                    else     context.done(null, JSON.stringify(data));
    });
}

exports.handler = function(event, context) {
    getMetricStatics(event, context);
};
```

# Push Notifications
<img width="843" alt="スクリーンショット 2015-11-27 0.33.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/0e0f88b9-d40c-000c-a0b8-15e9a4b00a8e.png">
Push通知の設定もmobile Hubの設定画面上から出来ます。
iPhoneアプリの場合はデベロッパー登録をAppleに申請して証明書をMobile Hub(SNS)にアップロードして上げる必要があります。AWS側の設定は特にコード等書く必要はなくこれで完了します。

# アプリのビルド
<img width="946" alt="スクリーンショット 2015-11-27 0.41.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/48d41fb6-5449-aaca-8207-cffa92fd0e78.png">
AWS側で必要な設定が終われば、Mobile Hubのbuildメニューからアプリのソースコードを生成します。ローカルにダウンロードして後はアプリの開発です。

# アプリ開発
後はゴリゴリとアプリ開発です。アプリ開発と言ってもビジネスロジックはLambdaで構成しているため、画面の開発とどのタイミングでLambdaに命令をおくるかというAction部分の開発になります。

Mobile Hubからダウンロードしたデフォルトの状態では[AWS Mobile Hubで作ったスマホアプリでAMIMOTOを起動させる](http://qiita.com/horike37/items/6d383962cfa6b752cbd7)にて紹介しているようなサンプルのアプリがダウンロードされた状態になります。これを好きなUIになるようにフォークして開発するイメージです。

以下はインスタンススタートを実施するアプリのコードの例。
ボタンをタップしたら指紋認証を挟んで、通過後にLambdaのインスタンスをスタートさせるLambdaファンクションにリクエストを送ります。

```objc:amimotoviewcontroller.m
- (IBAction)startInstance:(id)sender {
    LAContext *myContext = [[LAContext alloc] init];
    NSError *error = nil;
    
    // 端末が指紋認証を使えるかどうか判定
    if([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
        NSString *myLocalizedReasonString = @"指紋認証せんかい！";
        [self.activityIndicator startAnimating];
        // 指紋認証画面を表示
        [myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
                  localizedReason:myLocalizedReasonString
                            reply:^(BOOL success, NSError *error) {
                                
                                if(success) {
                                    
                                    // 指紋認証成功
                                    NSString *functionName = @"start-instance";
                                    
                                    NSLog(@"Function Name: %@", functionName);
                                    
                                    NSError *jsonError = nil;
                                    NSString *jsonInput = [NSString stringWithFormat:@"{\"instance_id\":\"%@\"}", self.instanceId];
                                    NSData *jsonData = [jsonInput dataUsingEncoding:NSUTF8StringEncoding];
                                    NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData
                                                                                               options:NSJSONReadingAllowFragments
                                                                                                 error:&jsonError];
                                    NSLog(@"Json Input: %@", jsonInput);
                                    
                                    [[AWSCloudLogic sharedInstance] invokeFunction:functionName withParameters:parameters
                                                               withCompletionBlock:^(id result, NSError *error) {
                                                                   dispatch_async(dispatch_get_main_queue(), ^{
                                                                       [self.activityIndicator stopAnimating];
                                                                       if (error != nil) {
                                                                           
                                                                           UIAlertView *alert =
                                                                           [[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"DISMISS" otherButtonTitles:nil];
                                                                           [alert show];
                                                                       } else {
                                                                           self.instanceStatusLabel.text = @"インスタンスステータス：running";
                                                                           self.instanceStatus = @"running";
                                                                       }
                                                                   });
                                                               }];
                                } else {
                                    [self.activityIndicator stopAnimating];
                                }
                            }];
    } else {
        // 指紋認証を使えない端末の処理
        // 指紋認証を使える端末だが、ユーザーが指紋認証機能をオフにしているケースの処理
        
    }
}
```

# 完成品
クライアント名がわかるものは伏せ字にしてますが以下の様な感じ！

### 管理してるAMIMOTOの一覧画面
<img width="432" alt="スクリーンショット 2015-11-27 1.23.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/8487d03c-b8d1-fa9c-ff1c-933aba5f881d.png">


### 詳細画面
<img width="329" alt="スクリーンショット 2015-11-27 1.21.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/65478/a9401767-5273-cbe1-a038-93d782c85182.png">


### Push通知来た時
<img width="375" alt="IMG_0354.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/65478/25a3526a-e2fc-fe55-f9e5-356dd7e69bed.jpeg">


# まとめ
とこんな感じで社内のマネージドホスティングのサーバを管理できるようにしました。
開発者としての率直な印象として、Mobile Hubが認証周りやIAM権限周りを一通り面倒見てくれるところが素晴らしいなと思いました。

また、Lambdaでビジネスロジックを作れたので、アプリはUIとActionを作るだけで完結するというのもアプリ開発初心者の僕からすればすごくとっつきやすいです。

やりたいことだけをシンプルにやるっていうサーバレスアーキテクチャの本質から言えばすごく理にかなっていて効率的だと思いました。

それではみなさん、よいサーバレスライフを！