PHPerだけどAWS STSを活用してSaaSをマルチテナント化した

本記事はcloudpack あら便利カレンダー2017の06/07(水)の記事です。

AWSアカウント:プロジェクト = 1:n なSaaSをβでリリースしてたけど、本番稼働にあたってAWSの請求情報わけられないじょん!
っていうヤバさを解決したい人生でした。

経緯

S3(Web) - EC2(API) - Device Farm
ユーザ認証:Cognito User Pools/DynamoDB
  1. ひとつのアカウント内のDevice FarmをSaaSで提供したい
  2. こんな感じのアーキテクチャ構想で組んで、これでいけるぜ!ってなった
  3. Device Farmには複数のProjectを立てることができる
  4. Cognitoに登録したユーザとProjectのヒモ付はDynamoDBで行っている
  5. サービスの課金形態的に、Projectごとの利用時間を計算する必要がある
  6. そんなことすっかり忘れてた!!
  7. 四則演算絶対やりたくない == ズレたときに燃えるのが目に見えてる
  8. Device Farmを別のアカウントにわけちゃおうぜ!!

どうやって実現したか

AWSには、他アカウントのリソースにアクセスする仕組みが用意されているため、それを使って実現しました。
STSの仕組みについてはこちらを参考にしてください。

  1. Cognitoのログインユーザーを特定して
  2. それをキーにDynamoDBからRoleのARNをひいて
  3. そのアカウントのDeviceFarmClientを取得する

こうすることで、

  • 請求情報はクライアント毎のアカウントに紐づくため請求処理も楽ちんに!!(ログインして請求情報見るだけ)
  • 今までプログラム上で「このProjectはこのユーザと紐付けて…」みたいな認証処理が不要に!! == セキュアに!

みたいなメリットが得られました!便利だね!すごいね2017年!

結論

AWS STS AssumeRoleつかったクロスアカウントアクセスは便利

コードのっけておきます

なんとなくstaticなSingletonで実装しました。

呼び出し側.php
<?php

use App\AwsClient;

$arn = ''; // なんかしらとってくる
$DeviceFarmClient = AwsClient::DeviceFarm($arn);

return $DeviceFarmClient->listProjects()->toArray();
呼び出され側.php
<?php

namespace App;

use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
use Aws\Credentials\AssumeRoleCredentialProvider;
use Aws\DeviceFarm\DeviceFarmClient;
use Aws\Sts\StsClient;

class AwsClient
{
    private static $DeviceFarm;

    private function __construct()
    {
    }

    final function __clone()
    {
        throw new \Exception('__clone() is not allowed'); 
    }

    public static function DeviceFarm($arn = null)
    {
        if (!self::$DeviceFarm) {
            $assumeRoleCredentials = new AssumeRoleCredentialProvider([
                'client' => new StsClient([
                    'region' => 'us-west-2',
                    'version' => 'latest'
                ]),
                'assume_role_params' => [
                    'RoleArn' => $arn,
                    'RoleSessionName' => 'service-' . time(),
                ]
            ]);

            self::$DeviceFarm = new DeviceFarmClient([
                'region' => 'us-west-2',
                'version' => 'latest',
                'credentials' => $assumeRoleCredentials
            ]);
        }

        return self::$DeviceFarm;
    }
}

参考:http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/credentials.html#assume-role-credentials

続きを読む

AWS Batchを使ってバッチ処理を実装してみた。

今回やりたかったこと

  • 社内で適用しているセキュリティのSaaSサービスのアップデートの自動化をしたい
  • 処理内容は、基本的にapiをコールしてこねこねしていくだけ

Lambdaでいいのでは?

  • SaaSが提供しているapiサーバーが、アクセス集中時だと5分経ってもレスポンスを返してくれない

そこでAWS BATCH

AWS BATCHとは

  • Job queueを受けた段階で、予め指定しておいたスペック(スペックが足りなかったら自動で最適なものを立ててくれるらしい?)のEC2を立ち上げてECRからコンテナイメージを持ってきてタスク実行してくれる
  • スケジュールを設定して実行してくれる!とかは無さそう。

アーキテクチャ

スクリーンショット 2017-03-19 22.05.34.png

Dockerfileはこんな感じ

FROM centos:latest
RUN curl -kl https://bootstrap.pypa.io/get-pip.py | python
RUN pip install awscli
RUN yum install -y git
ADD init.sh /opt/
CMD ["sh","/opt/init.sh","test"]

事前に処理実行に必要なコードだったりシェルスクリプトなんかは、ECRに含めておく。
シェルには、コードコミットから実行ソースをcloneするのに必要なssh key(事前にKMSとかで暗号化しておいたもの)、復号化処理、処理実行とかを書いておく。

あとは、Job queueを送ってあげれば、自動でEC2を立ち上げ、コンテナをマウントして最新のソースコードをcloneしてきて、処理を実行してくれます。
自動で立ち上がったEC2はタスク処理終了後(CMDのとこのやつ)、1時間単位の課金が切れるタイミング?で自動で削除されます。(立ち上がりっぱなしとかの心配なし!)

job queueに送信
aws --profile <profile> --region <region> batch submit-job --job-name <好きな名前> --job-queue <job queueの名前> --job-definition <定義したjob>

結果については、 Jobsの画面で確認することができます。
スクリーンショット 2017-03-19 22.17.59.png

まとめ

先日参加した「JAWS-UG KOBE」のコンテナまつりでHiganWorks sawanobolyさんがElasticDockerRunって呼んでるって言ってました。
DockerImageを基にいい感じに実行してくれるが、逆にBatch?なのかって印象でした。
もし、スケジュール実行で定期的にbatch処理をしたい!とかでれば、lambdaなんかを使ってqueueに送信してやるといいかなぁと思いました。

追記

シェル内にkmsで暗号化したssh keyを含めているのですが、出来れば外出ししたい。。
パラメーターストアとかに出せるかと思ったのですが、文字数制限の関係で無理でした。

続きを読む