Amazon S3 に Git LFS サーバを超簡単に立てる

元ネタはこちら。
Serverless Git LFS for Game Development – Alan Edwardes
Amazon API Gateway + Amazon Lambda + Amazon S3 を使うんだそうです。

準備: AWS のアカウントをとる

AWSのアカウントがなければとります。
作成の流れは 公式のドキュメント でだいじょうぶです。

アカウントをとったら初期設定しておきます。以下のエントリが参考になりました。
AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ – Qiita

サーバ作成手順

Screen_Shot_2018-01-21_at_22_29_22-fullpage.png

  • 「次へ」をクリックします → オプションの設定ページはスルーで「次へ」をクリックします。
  • 「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックして「作成」をクリックします。

Screen_Shot_2018-01-21_at_23_05_08-fullpage.png

  • スタックのリストに新しいスタックができて(スタックがなければリロードして下さい)、「CREATE_COMPLETE」と表示されたら成功です。

Screen_Shot_2018-01-21_at_23_10_18-fullpage.png

  • スタックの詳細画面に入って、出力を確認すると、Git LFSのエンドポイントのURLが表示されているので、リンクをコピーします。

Screen_Shot_2018-01-21_at_22_34_53-fullpage.png

Git LFS に lfs.url を設定

Git LFS は導入してあることとします。

以下のコマンドで、 .lfsconfig を作成します。

git config -f .lfsconfig lfs.url <表示されたエンドポイントのURL>

git lfs env で確認して、Endpoint= の行が設定したURLになってたらOKです。

git push すると、S3の方にLFSオブジェクトがpushされるようになります。

問題点

URL を見ればわかると思いますが、この方法で作成するとリージョンが 欧州 (アイルランド) に作成されます。
東京リージョンにS3を立てたいと思って、 URL の region=eu-west-1 の部分を region=ap-northeast-1 に変えてみましたが、リージョンを変えると成功しないみたいです。

alanedwardes/Estranged.Lfs – Github

どなたか検証お願いします。 m(_ _)m

続きを読む

そのAWS Lambdaの.NET Core、1.0から2.0にアップデートしませんか?

概要

  • 2018年1月15日、とうとうAWS Lambdaでの.NET Coreで2.0がサポートされるようになりました🎉
  • 早速バージョンアップしてみましょう。

ソリューションのアップデート

細かいことはおいておいて、まずはアップデートしてみましょう。

題材はこれです。

CloudWatch Logsに書き込まれた内容をSlackとGoogle Homeで通知するAWS Lambda using dotnet

がんばった割に鳴かず飛ばずだったこの記事で紹介している、CloudWatch Logsを監視してSlackとGoogle Homeに通知するLambdaです。

https://github.com/toshi0607/ErrorNotificationLambda

プロジェクト構成

ErrorNotificationLambda/
├ErrorNotificationLambda/
│ ├Properties/
│ ├aws-lambda-tools-defaults.json
│ ├ErrorNotificationLambda.csproj
│ ├Function.cs
│ └Readme.md
└ErrorNotificationLambda.Tests/
  ├Properties/
  ├ErrorNotificationLambda.Tests.csproj
  ├FunctionTest.cs
  └LaunchSettingsFixture.cs
“`

変更箇所

csprojを変更します。

ErrorNotificationLambda.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <WarningLevel>0</WarningLevel>
  </PropertyGroup>

  <PropertyGroup>
    <OutputType>Library</OutputType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.CloudWatchLogsEvents" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="1.8.0" />
  </ItemGroup>

</Project>

これを

<TargetFramework>netcoreapp1.0</TargetFramework>

こう

<TargetFramework>netcoreapp2.0</TargetFramework>

以上。

Visual Studio 2017から編集する場合は、該当プロジェクトを右クリックすると「[プロジェクト名].csprojの編集」が選択できます。

4csproj.PNG

aws-lambda-tools-defaults.jsonにデフォルトのデプロイ設定を書いている場合、そちらもお忘れなく。

Publish to Lambdaするときに設定を保存しても書き換わってくれます。

また、Serverless Applicationのテンプレートからプロジェクトを作成した場合はCloudFormationのリソースもdotnetcore2.0に変更しましょう。

AWS Toolkit for Visual Studio 2017のアップデート

AWS Toolkit for Visual Studio 2017はVisual Studioからデプロイしたり、テンプレートを選択したりできるようにする拡張機能です。

今回.NET Core 2.0系対応が入った拡張機能のバージョンは1.14.0.0ですが、僕のVisual Studioにはまだ更新が降ってきていなかったので、マーケットプレースから直接.vsixをダウンロードし、インストールしました。

現在インストールされているバージョンは「ツール」->「拡張機能と更新プログラム」->「インストール済み」の中にあるAWS Toolkit for Visual Studio 2017を選択すると確認することができます。

61.14.PNG

バージョンアップすることで変更される大事なポイントは次の二点でしょう。

  • Language Runtimeで.NET Core v2.0が選択できる
  • Memoryが3008MBまで選択できる

メモリはまた上限上がったんですねw 年末のLambda共通のアップデート時点ではポータルで選択できる上限が上がっているにもかかわらず、「Upload Lambda Function」ではそれが反映されていませんでした。

72.0.PNG

8memory.PNG

動作確認

ローカルでテスト通るか?

デプロイ後ポータルでのバージョンが2.0になっているか?

82.0.PNG

本番環境のLambdaに対してテストイベントを発行した期待した結果になるか?

9vs.PNG

9slack.PNG

特に問題はなかったです。

アップデートの詳細

You can now develop AWS Lambda functions in C# using the .Net Core 2.0 runtime! Get started easily with the AWS Toolkit for Visual Studio. https://t.co/uxmrKodaUY pic.twitter.com/h442a6DwEZ

— Amazon Web Services (@awscloud) 2018年1月16日

AWS Lambda Supports C# (.NET Core 2.0)

AWS Lambda .NET Core 2.0 Support Released

一番下のが実際に開発してる人が書いた記事です。

aws-lambda-dotnetは大体この人が書いていると言っても過言ではないし、PRを出すととても丁寧に対応してくれます。

https://github.com/aws/aws-lambda-dotnet/graphs/contributors

VSTSのAWS周りタスクがリリースされた記事も英語版が出て2週間後くらいに日本語版が出たので、今回もきっとそのうち日本語版が出ることでしょう。

とても雑にまとめると、

  • .NET Core 2.0サポートすると、.NET Standard 2.0ベースでライブラリを作成するときより多くの.NET APIを利用できるので、既存の.NET資産をより使い回しやすくなる
  • テンプレはもう2.0ベースにアップデートしているので、AWS Toolkitのテンプレート使ってVisual Studioからデプロイしてしまうのが一番楽
  • Amazon.Lambda.Toolsも2.0がリリースされ、パッケージングするにあたって2.0ランタイムで必要となるファイルも生成できる
  • Amazon.Lambda.Templatesも2.0.0(同日に2.0.1がリリースされていた…!)がリリースされ、Visual Studioがなくてもすべてのテンプレがコマンドで利用できるようになった
  • プログラミングモデルは変わっていたいので、既存の1.0系プロジェクトもシュッとアップデートできる
  • AWS Tools for VSTSもアップデートされて2.0対応されたが既存のタスクを更新する必要はない

まとめ

「.NET Core 2.0対応」が目につきますが、実際は周辺のツールのアップデートがとてもとてもいい感じです。

続きを読む

SSL証明書の有効期限監視をLambda+CloudWatchで実装する

SSL証明書の有効期限をLambdaで取得し、CloudWatchにカスタムメトリクスを送り、CloudWatch Alarmで通知する

AWS上のEC2インスタンスや各種サービスの監視を行う際、CloudWatch Alarmを活用しているケースは多いと思います。
このような場面を想定し、Lambdaで毎日SSL証明書の有効期限チェックを行い、CloudWatchに各ドメインの残日数を送り、その日数をターゲットとしたCloudWatch Alarmを設定することができるようにしたいと思います。

環境

ランタイム:node.js 6.10
開発環境:AWS Cloud9

監視対象の管理

監視対象のドメインは適宜増減することが想定されます。
これらを簡単に管理するために、今回はAWS Systems Manager のパラメータストアにて管理を行います。

パラメータストアに、下記のようなパラメータを登録します。

項目 内容
キー名 /monitor/certificate/domains
タイプ 文字列
[ “hogehoge.com”, “example.com” ]

上記のようにチェックを行うドメインをJSON配列形式で設置します。

Lambdaコード

下記ので実装できます。
※child-process-promise を使用していますが、モジュールのインストールを別途行わなくてもよい
 child_processを使っても良いと思います。(全てをPromiseで統一したかったのであえてchild-process-promiseを使っています)

index.js
const AWS = require('aws-sdk');
var SSM;
var CW;

exports.handler = (event, context, callback) => {
  if(SSM===undefined){
    SSM = new AWS.SSM({region: 'ap-northeast-1'});
  }
  if(CW===undefined){
    CW = new AWS.CloudWatch({region: 'ap-northeast-1'});
  }

  Promise.resolve()
  .then(()=>{
    return new Promise((resolve)=>{
      let ssm_params = {
        Name: "/monitor/certificate/domains"
      };
      let ssm_parameter = SSM.getParameter(ssm_params).promise();
      ssm_parameter.then((data)=>{
        resolve(JSON.parse(data.Parameter.Value));
      });
    });
  })
  .then((domains)=>{
    return new Promise((resolve)=>{
      let check_expire_list = [];
      domains.forEach((domain)=>{
        check_expire_list.push(checkExpire(domain));
      });
      Promise.all(check_expire_list)
      .then((result_valid)=>{
        resolve(result_valid);
      });
    });
  })
  .then((valid_list)=>{
    return new Promise((resolve)=>{
      let metric_list = [];
      valid_list.forEach((result)=>{
        var params = {
          MetricData: [
            {
              MetricName: 'valid_days',
              Dimensions: [
                {
                  Name: 'domain',
                  Value: result.domain
                }
              ],
              Unit: 'None',
              Value: result.valid_days
            }
          ],
          Namespace: 'SSLCertificate'
        };
        metric_list.push(CW.putMetricData(params).promise());
      });
      Promise.all(metric_list)
      .then((data)=>{
        resolve();
      });
    });
  })
  .then(()=>{
    callback();
  });
};

var checkExpire = (domain)=>{
  return new Promise((resolve)=>{
    let exec = require('child-process-promise').exec;
    let cmd = "openssl s_client -connect " + domain + ":443 -servername " + domain + " < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/^  *//g' | sed -e 's/Not.*: //g'";
    let now = new Date();
    exec(cmd)
    .then((result)=>{
      let not = result.stdout.split("n");
      let expire_date = new Date(not[1]);
      let valid_days = Math.floor((expire_date - now) / 1000 / 3600 / 24);
      console.log(domain+" の残り日数は "+valid_days + " です");
      resolve({'domain': domain,'valid_days': valid_days});
    });
  });
};

SAM Template(CloudFormation)

上記のコードをデプロイするためのSAMテンプレートとして、下記のテンプレートでデプロイすることで、CloudWatchEventsにて毎日実行する設定を同時に行われます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: SSL certificate monitoring function.
Resources:
  monitorCertExpire:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: monitorCertExpire/index.handler
      Runtime: nodejs6.10
      Description: ''
      MemorySize: 256
      Timeout: 300
      Events:
        CheckExpire:
          Type: Schedule
          Properties:
            Schedule: rate(1 day)
            Input: "{}"

CloudWatch

上記をデプロイすることで、CloudWatchメトリクスに 名前空間:SSLCertificate メトリック:valid_days で有効期限切れまでの残り日数が毎日送信されます。

このメトリックデータを使用し、間隔:1日 統計:最小 期間:1中1のデータポイント にて、アラートさせたい日数をしきい値としてアラームを設定することでこれまで通り他の監視と同様に扱うことができます。

参考にさせていただきました

https://qiita.com/zwirky/items/25b1a66dac534f67ca03
https://blog.manabusakai.com/2016/07/lambda-cert-expire/

続きを読む

AWS CLIに複数のAWSアカウントを登録する方法

複数のAWSを利用するプロジェクトに携わっている場合、複数のAWSアカウントをCLIで操作する可能性が出てきます。
Secret Keyは作成時しか見えませんので、毎回AWSにログインしてSecret Keyを作り直すこともナンセンスです。
本記事では、AWS CLIに複数のAWSアカウントを登録し、再度configする手間を省く方法をまとめます。

AWS CLIのインストール

AWSの公式ドキュメントを参考にinstallを行います。
https://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html

初期設定

defaultで利用するアカウントを登録します。

$ aws configure
AWS Access Key ID [None]: ${Your Default ID}
AWS Secret Access Key [None]: ${Your Default Secret Key}
Default region name [None]: ${Region}
Default output format [None]: json

AWS CLIに複数のアカウントを登録する

AWS CLIは–profileオプションを利用することで、複数のアカウントを登録することが可能です

$ aws configure --profile xxxx
AWS Access Key ID [None]: ${Your ID}
AWS Secret Access Key [None]: ${Your Secret Key}
Default region name [None]: ${Region}
Default output format [None]: json

では、どのようにcredentialなどは登録されているのでしょうか。
AWS CLIのconfig、およびcredentialを保存しているfileを確認してみましょう。

~/.aws
$ tree ~/.aws
.aws
├── config
└── credentials

まずはconfigから確認してみましょう。

~/.aws/config
[default]
output = json
region = ap-northeast-1
[profile xxxx]
output = json
region = ${Region}

[profile xxxx]といった項目が追加され、先ほど入力した情報と同様の情報が格納されていることが確認できます。
credentialsの方も確認してみましょう。

~/.aws/credentials
[default]
aws_access_key_id = ${Your Default ID}
aws_secret_access_key = ${Your Default Secret Key}
[xxxx]
aws_access_key_id = ${Your ID}
aws_secret_access_key = ${Your Secret Key}

こちらも先ほど入力した情報と同様の情報が格納されていることが確認できました。

AWS CLIの利用方法

–profileオプションを利用することによって、~/.aws以下に保存されている情報から、–profileの引数と同様の項目からkeyなどの情報を参照して利用します。
以下の例は、s3バケットの一覧をアカウントを変えて表示させています。
アカウントが変わることで、s3のバケットが変更されるのがわかると思います。

s3バケットの一覧が変更されていることを確認
$ aws s3 ls
$ aws s3 ls --profile xxxx

まとめ

今回はAWS CLIに複数のAWSアカウントを登録する方法をまとめました。
最近はAWS CLIだけではなく、Serverless frameworkやterraformを利用した構築方法もありますので、そちらの方も随時まとめていけたらと思います。

続きを読む

Glueの使い方的な⑦(Step Functionsでジョブフロー)

Step FunctionsでGlueのジョブフローを作る

Glueの使い方的な③(CLIでジョブ作成)“(以後③と書きます)で書いたように、現在Glueのジョブスケジュール機能は簡易的なものなので、複雑なジョブフロー形成には別のスケジューラーが必要になる場合もあります。
例えばGlueのクローラーとGlueジョブもそれぞれにスケジュール機能があり統合したジョブフローを作ることがGlueだけでは出来ません(例えばクローラーを実行し終わったらジョブを実行するとか)。今回はサーバーレスなジョブフローのサービスであるStep Functionsを使って、クローラーを実行し正常終了したら後続のジョブを実行するというフローを作ってみます。

全体の流れ

  • Glue処理内容
  • StepFunctionsの処理内容
  • 前準備
  • Step FunctionsでStateMachine作成
  • 実行

処理内容

Glueの使い方的な①(GUIでジョブ実行)“(以後①と書きます)で実行したものと同じクローラーとジョブを使います。入力データも出力結果も①と同じです。
今回行うのはGlueクローラー処理が終わったら次のGlueジョブ処理開始というジョブフロー形成です。

あらためて①のクローラーとジョブの処理内容は以下の通りです

クローラーの内容

入力のCSVファイルからスキーマを作成します

ジョブの内容

“S3の指定した場所に配置したcsvデータを指定した場所にparquetとして出力する”

Step Functionsを使ったジョブフローの内容

図の四角をStep Functionsでは”State”と呼びます。処理の1単位と思ってください。

ジョブフローは以下のような形です。

Stateごとに流れを説明します

  • “Submit Crawler Job”でLambdaを使いGlueクローラーを実行
  • “Wait X Seconds”で指定時間待つ
  • “Get Crawler Job Status”でLambdaを使いGlueクローラーの状態をポーリングして確認
  • “Job Complete?”で状態を判定して結果によって3つに処理が分岐
    • 失敗なら”Job Failed”エラー処理
    • 終了なら”Run Final Glue Job”でLambdaを使い後続のGlueジョブを実行
    • 処理中なら”Add Count”でLambdaを使いカウンタをインクリメント。
      • “Add Count”の後”Chk Count”でカウンタをチェックし3回以上になっていたら”Job Failed Timeout”でタイムアウト処理、3未満なら”Wait X Seconds”に戻りループ処理

スクリーンショット 0030-01-13 21.47.05.png

前準備

①と同じです

今回使うサンプルログファイル(19件)

csvlog.csv
deviceid,uuid,appid,country,year,month,day,hour
iphone,11111,1,JP,2017,12,14,12
android,11112,1,FR,2017,12,14,14
iphone,11113,9,FR,2017,12,16,21
iphone,11114,007,AUS,2017,12,17,18
other,11115,005,JP,2017,12,29,15
iphone,11116,001,JP,2017,12,15,11
pc,11118,001,FR,2017,12,01,01
pc,11117,009,FR,2017,12,02,18
iphone,11119,007,AUS,2017,11,21,14
other,11110,005,JP,2017,11,29,15
iphone,11121,001,JP,2017,11,11,12
android,11122,001,FR,2017,11,30,20
iphone,11123,009,FR,2017,11,14,14
iphone,11124,007,AUS,2017,12,17,14
iphone,11125,005,JP,2017,11,29,15
iphone,11126,001,JP,2017,12,19,08
android,11127,001,FR,2017,12,19,14
iphone,11128,009,FR,2017,12,09,04
iphone,11129,007,AUS,2017,11,30,14

入力ファイルをS3に配置

$ aws s3 ls s3://test-glue00/se2/in0/
2018-01-02 15:13:27          0 
2018-01-02 15:13:44        691 cvlog.csv

ディレクトリ構成

in0に入力ファイル、out0に出力ファイル

$ aws s3 ls s3://test-glue00/se2/
                           PRE in0/
                           PRE out0/
                           PRE script/
                           PRE tmp/

ジョブのPySparkスクリプト

se2_job0.py
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job

## @params: [JOB_NAME]
args = getResolvedOptions(sys.argv, ['JOB_NAME'])

sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
## @type: DataSource
## @args: [database = "se2", table_name = "se2_in0", transformation_ctx = "datasource0"]
## @return: datasource0
## @inputs: []
datasource0 = glueContext.create_dynamic_frame.from_catalog(database = "se2", table_name = "se2_in0", transformation_ctx = "datasource0")
## @type: ApplyMapping
## @args: [mapping = [("deviceid", "string", "deviceid", "string"), ("uuid", "long", "uuid", "long"), ("appid", "long", "appid", "long"), ("country", "string", "country", "string"), ("year", "long", "year", "long"), ("month", "long", "month", "long"), ("day", "long", "day", "long"), ("hour", "long", "hour", "long")], transformation_ctx = "applymapping1"]
## @return: applymapping1
## @inputs: [frame = datasource0]
applymapping1 = ApplyMapping.apply(frame = datasource0, mappings = [("deviceid", "string", "deviceid", "string"), ("uuid", "long", "uuid", "long"), ("appid", "long", "appid", "long"), ("country", "string", "country", "string"), ("year", "long", "year", "long"), ("month", "long", "month", "long"), ("day", "long", "day", "long"), ("hour", "long", "hour", "long")], transformation_ctx = "applymapping1")
## @type: ResolveChoice
## @args: [choice = "make_struct", transformation_ctx = "resolvechoice2"]
## @return: resolvechoice2
## @inputs: [frame = applymapping1]
resolvechoice2 = ResolveChoice.apply(frame = applymapping1, choice = "make_struct", transformation_ctx = "resolvechoice2")
## @type: DropNullFields
## @args: [transformation_ctx = "dropnullfields3"]
## @return: dropnullfields3
## @inputs: [frame = resolvechoice2]
dropnullfields3 = DropNullFields.apply(frame = resolvechoice2, transformation_ctx = "dropnullfields3")
## @type: DataSink
## @args: [connection_type = "s3", connection_options = {"path": "s3://test-glue00/se2/out0"}, format = "parquet", transformation_ctx = "datasink4"]
## @return: datasink4
## @inputs: [frame = dropnullfields3]
datasink4 = glueContext.write_dynamic_frame.from_options(frame = dropnullfields3, connection_type = "s3", connection_options = {"path": "s3://test-glue00/se2/out0"}, format = "parquet", transformation_ctx = "datasink4")
job.commit()

入力のCSVデータのスキーマ

クローラーによって作成されるスキーマ

スクリーンショット 0030-01-13 22.01.43.png

StepFunctionsでStateMachine作成

StepFunctionsは一連のジョブフローをJSONで定義しこれを”StateMachine”と呼びます。
StateMachine内の処理の1つ1つの四角をStateと呼びます。処理の1単位です。
このJSONの記述はASL(AmazonStatesLanguages)と呼ばれStateTypeとしてChoice(分岐処理)やWait(待ち)やParallel(並列実行)などがJSONだけで表現出来ます。またTaskというStateTypeからはLambdaやアクティビティ(EC2からStepFunctionsをポーリングする)を定義できます。前述の通り今回はLmabdaを使います。

マネージメントコンソールからいくつかあるテンプレートを元に作ることも出来ますが、カスタムでJSONを一から作ることもできます。

新規StateMachine作成画面
“Author from scrach”で一からJSON作成

スクリーンショット 0030-01-14 10.24.59.png

“Template”を選ぶとASLのStateパターンのいくつかのテンプレが選べます

スクリーンショット 0030-01-14 10.28.14.png

左側の”コード”部分にJSONを書き、右側の”ビジュアルワークフロー”の部分にJSONコードで書いたフローがビジュアライズされます

スクリーンショット 0030-01-14 10.29.50.png

StateMachine

今回のStateMachieのJSONは以下です。
内容は前述の通りです。
※[AWSID]のところは自身のAWSIDと置き換えてください

{
  "Comment": "A state machine that submits a Job to Glue Batch and monitors the Job until it completes.",
  "StartAt": "Submit Crawler Job",
  "States": {
    "Submit Crawler Job": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:[AWSID]:function:glue-test1-cr1",
      "ResultPath": "$.chkcount",
      "Next": "Wait X Seconds",
      "Retry": [
        {
          "ErrorEquals": ["States.ALL"],
          "IntervalSeconds": 120,
          "MaxAttempts": 3,
          "BackoffRate": 2.0
        }
      ]
    },
    "Wait X Seconds": {
      "Type": "Wait",
      "SecondsPath": "$.wait_time",
      "Next": "Get Crawler Job Status"
    },
    "Get Crawler Job Status": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:[AWSID]:function:glue-test1-crcheck",
      "Next": "Job Complete?",
      "InputPath": "$",
      "ResultPath": "$.response",
      "Retry": [
        {
          "ErrorEquals": ["States.ALL"],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2.0
        }
      ]
    },
      "Job Complete?": {
      "Type": "Choice",
      "Choices": [{
          "Variable": "$.response",
          "StringEquals": "FAILED",
          "Next": "Job Failed"
        },
        {
          "Variable": "$.response",
          "StringEquals": "READY",
          "Next": "Run Final Glue Job"
        }
      ],
      "Default": "Add Count"
        },
    "Add Count": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:[AWSID]:function:glue-test1-addcount",
      "Next": "Chk Count",
      "InputPath": "$",
      "ResultPath": "$.chkcount",
      "Retry": [
        {
          "ErrorEquals": ["States.ALL"],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2.0
        }
      ]
    },
      "Chk Count": {
      "Type": "Choice",
      "Choices": [{
          "Variable": "$.chkcount",
          "NumericGreaterThan": 3,
          "Next": "Job Failed Timeout"
        }],
      "Default": "Wait X Seconds"
    },
    "Job Failed": {
      "Type": "Fail",
      "Cause": "Glue Crawler Job Failed",
      "Error": "DescribeJob returned FAILED"
    },
        "Job Failed Timeout": {
      "Type": "Fail",
      "Cause": "Glue Crawler Job Failed",
      "Error": "DescribeJob returned FAILED Because of Timeout"
    },
    "Run Final Glue Job": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:[AWSID]:function:glue-test1-job1",
      "End": true,
      "Retry": [
        {
          "ErrorEquals": ["States.ALL"],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2.0
        }
      ]
    }
  }
}

Lambda

今回使うLambdaは4つです。流れも振り返りながら見ていきます
書き方はいろいろあるし今回はエラーハンドリングも甘いのであくまでも動きのイメージをつかむための参考程度にしてください。最後のGlueジョブの実行についてはジョブの終了判定とかはしてないです。

“Submit Crawler Job”

GlueのAPIを使ってクローラーのStartを行う

glue-test1-cr1
# coding: UTF-8

import sys
import boto3
glue = boto3.client('glue')

def lambda_handler(event, context):
    client = boto3.client('glue')
    response = client.start_crawler(Name='se2_in0')
    return 1

“Wait X Seconds”

Waitで指定秒数待つ

“Get Crawler Job Status”

GlueのAPIを使ってクローラーのステータスを取得します

glue-test1-crcheck
# coding: UTF-8

import sys
import boto3
import json
glue = boto3.client('glue')

def lambda_handler(event, context):
    client = boto3.client('glue')
    response = client.get_crawler(Name='se2_in0')
    response = response['Crawler']['State']
    return response

“Job Complete?”

Choiceで取得したステータスが、”READY”なら正常終了、”FAILED”なら失敗、それ以外は実行中の分岐処理

“Job Failed”

ステータスが失敗なら
FailでStepFunctionsをエラーさせます

“Run Final Glue Job”

ステータスが正常終了なら
GlueのAPIを使ってジョブをStartします

glue-test1-job1
# coding: UTF-8

import sys
import boto3
import json
glue = boto3.client('glue')

def lambda_handler(event, context):
    client = boto3.client('glue')
    response = client.start_job_run(
    JobName='se2_job0')
    return response['JobRunId']

“Add Count”

クローラーがまだ実行中なら
カウンタにインクリメントします

glue-test1-addcount
# coding: UTF-8

import sys
import boto3
import json
glue = boto3.client('glue')

def lambda_handler(event, context):
    chkcount = event["chkcount"]
    chkcount = chkcount + 1

    return chkcount

“Chk Count”

choiceでカウンタが3未満か3以上かをチェックします

“Job Failed Timeout”

Failでカウンタが3以上だった時のエラー処理

“Wait X Seconds”

3未満の場合はここに戻りループ処理

実行

Step Functionsを実行

作成したStateMachineを選び”新しい実行”をクリック

スクリーンショット 0030-01-14 10.54.54.png

JSONに引数を入れて”実行の開始”をクリック
今回はJSON内で使う変数で”wait_time”を60秒で待ちの時間として入力しています

スクリーンショット 0030-01-14 10.55.52.png

実行状況

スクリーンショット 0030-01-14 10.59.44.png

CloudWatchイベントでスケジュール

あとは上記で作成したStateMachineをCloudWatchイベントでCRON指定すれば定期的実行されるジョブフローの完成です。This is Serverless!

スクリーンショット 0030-01-13 22.34.00.png

その他

今回はクローラー実行後にジョブ実行というシンプルなフローでしたが、Step Functionsは並列度を替えたり引数の受け渡しをしたり、さらにLambdaでロジックを書くことができるので自由度高く複雑なフローの作成が行えます。Glueとの相性はいいのではないでしょうか?

JSON部分も30分もあれば学習完了というカジュアルさがありLambdaを使ってAPI操作で様々なAWSの処理を繋げるのにはとてもいい印象です。

かなりシンプルな処理だったのですがコードがやや多い印象で、より複雑な処理になると結構大きいJSONになりそうで、JSONなのでコメント書けないとか少し大変な部分が出て来るのかもしれません。

バージョン管理を考えるとCliでの処理で運用したほうが良さそうですが、こういったサービスはGUIでの良さもあるのでどちらに比重を置いた運用がいいかは考慮が必要かもです

本文中で使ったカウンタのステート情報はDynamoDBなどに入れた方が良いかもです。

マイクロサービス化しやすいので、極力本来の処理のロジックをLambda側にやらせてそれ以外のフロー処理(分岐とかカウンタインクリメントとか)をJSONで書くのがいいと思います。今回カウンタはLambdaでやってしまいましたが。

ログはCloudWatchLogsに出ます

To Be Continue

TODO

参考

StepFunctions BlackBelt資料
https://www.slideshare.net/AmazonWebServicesJapan/20170726-black-beltstepfunctions-78267693

続きを読む

【神戸2/11】Alexa Day 2018にて当社の大石、坂本が登壇いたします

【Alexa Day 2018とは】 Alexa Day 2018は、米国をはじめとして世界中で爆発的な人気となっており、今後国内のAI技術に対して大きなインパクトを与えることが予想されるAIアシスタントの「Alexa」やこれを支えるServerlessアーキテクチャ・LEX・AIやMLなどの技術や最新情報の共有を目的として、AWSの日本のユーザー … 続きを読む

【レポート】サーバーレスアーキテクチャパターンとベストプラクティス #reinvent #ARC401 | Developers.IO

こんにちは、虎塚です。 re:Invent 2017のセッション「Serverless Architectural Patterns and Best Practices」(サーバーレスアーキテクチャパターンとベストプラクティス) の動画を視聴したのでレポートします。発表は、AWSのDrew DennisさんとMaitreya Ranganathさんでした。 本セッションでは、Webアプリケーション、データ … 続きを読む

Assume-role先のRoleの権限で(AWSの機能|vagrant|terraform|serverless|apex)を使いたい

背景

AWS CLIがAssumeRoleによる自動クレデンシャル取得とMFAに対応しました! | Developers.IO
awscliは上記で説明されているような記述を用いることにより、assume-role権限のあるroleへswitchしてコマンドを実行することができます。

しかし、この記述法は比較的最近導入されたこと、使用されることがそれほど多くないこと、内部的にSTSを使っているため自前で同様の仕組みを実装することは手間がかかることなどにより、通常 ~/.aws/credentials を見て自動的に認証情報を取得してくれるコマンドでもこのAssumeRole記法に対応してくれていないツールがあります(代表的なものにserverless, apex, terraform, vagrant-awsがあります)。

解決策

そこで、認証情報として環境変数が概ね優先されることを利用してラッパーコマンドを開発した人が結構います。
マイナーな欲求かつ簡単に実装できるため多数のリポジトリがありますが、以下がgithub上で自分が見つけた同様の目的のリポジトリ一覧です。

(※星の数は2018/01/09時点)

上記のリポジトリのうち、上2つは期待通り動くことを確認しました。
下3つはすみませんが動作確認をしていません。

結論

remind101/assume-role が期待通り動作しておりかつ星の数的にも定評があると思うので、機能的に問題なければそれを使えばいいと思います。

続きを読む

AWSとAzureとGCPを比較してみる – DB編

DBについて、AWSとAzureとGCPを比較してみました。

1. 新世代DB

AWS Azure GCP
新世代DB Aurora Cosmos DB Cloud Spanner
DBの種類 MySQL,
Postgresql
SQL (document DB),
MongoDB (document DB),
Gremlin (graph DB),
Azure Table(KVS),
Cassandra
オリジナルのリレーショナルDB
サーバレスか否か サーバあり サーバレス サーバレス
高可用性構成 / 負荷分散 Auroraレプリカ,
クロスリージョンレプリカ(MySQLのみ)
リージョン間フェイルオーバー,
予約済みスループット
リージョン内レプリケーション,
マルチリージョンレプリケーション
地理的範囲 リージョン(MySQLは別リージョンにレプリケーション可) グローバル グローバル
マルチマスター シングルマスター,
マルチマスター*
マスターになるリージョンは1個 マルチマスター

*プレビュー

DBの種類ですが、Auroraは手堅くMySQLとPostgresql、Cosmos DBはバラエティーにとんでいてドキュメントDB・KVS・グラフDBとCassandra、Cloud SpannerはオリジナルのリレーショナルDBとなっています。
Cloud Spannerはクライアントライブラリが各言語(C#,GO,Java**, node.js**,PHP**, Python**, Ruby)に対し用意されていますが、ORMの対応が気になるところです。
**ベータ

Cosmos DBとCloud Spannerはサーバレスですが、Auroraはインスタンスタイプを指定してインスタンスを構築します。また、拡張機能というよりは別物として、サーバレスタイプのAurora serverless*がプレビュー中です。

高可用性構成と負荷分散ですが、Auroraはリージョン内ではリードレプリカが障害時にマスターに昇格することで対応しています。MySQL版はクロスリージョンレプリケーション構成を取ることができますが、リージョン間で自動フェイルオーバーする仕組みはありません。
また、Auroraはマルチマスター機能であるAurora Multi-Master*が現在プレビュー中ですが、リージョン間でも利用可能になる予定があるとアナウンスされています。リリースされればグローバルで高可用性と負荷分散が簡単に実現できそうです。
Cosmos DBは、1個の書き込みリージョンを持つリージョン間フェイルオーバー***の仕組みで高可用性を実現しています。
Cloud Spannerはリージョン内レプリケーションとマルチリージョンレプリケーションの仕組みで高可用性を実現しています。1つのリージョンで構築する場合は、3個のread-writeレプリカを保持します。複数リージョンで構築する場合は、2個のread-writeレプリカを保持する2個のread-writeリージョン(と場合によってread-onlyリージョン)で構成されます。

***Microsoftのドキュメントではregional failoverをリージョン内フェイルオーバーと訳していますが、意味合いはリージョン間フェイルオーバーなので、ここではそのように表記しています。

2. リレーショナルDB

AWS Azure GCP
MYSQL互換 MySQL
/ MariaDB
Azure Database for MySQL* Google Cloud SQL for MySQL
高可用性構成 Multi-AZ,
クロスリージョンリードレプリカ
フェイルオーバーレプリカ
負荷分散 リードレプリカ,
クロスリージョンリードレプリカ
リードレプリカ
Postgresql Postgresql Azure Database for PostgreSQL* Google Cloud SQL for PostgreSQL**
高可用性構成 Multi-AZ,
クロスリージョンリードレプリカ
リージョナルインスタンス**
負荷分散 リードレプリカ,
クロスリージョンリードレプリカ
リードレプリカ**
SQL Server SQL Server Azure SQL Database
高可用性構成 Multi-AZ アクティブgeoレプリケーション
負荷分散 アクティブgeoレプリケーション
Oracle Oracle
高可用性構成 Multi-AZ
負荷分散

*プレビュー
**ベータ

・各クラウド間での違い-その1

AWSのMulti-AZとGCPのリージョナルインスタンス(PostgreSQL)は、スタンバイ側はリードの機能がないので負荷分散には利用できませんが、GCPのフェイルオーバーレプリカ(MySQL)はリードの機能があるので負荷分散にも利用できます。

3. NOSQL

AWS Azure GCP
KVS・ドキュメント ElastiCache(Memcached, Redis),
DynamoDB
Redis Cache,
Cosmos DB(Azure Table, SQL, MongoDB)
Cloud Datastore,
Cloud Bigtable
グラフ Neptune* Cosmos DB(Gremlin)
Cosmos DB(Cassandra)

*プレビュー

Neptuneの現時点のプレビューのAWSマネジメントコンソール画面はAmazon RDSとよく似ています。また裏でAuroraと同じ仕組みを利用しているそうなので、ひょっとしたらCosmos DBみたいに、Auroraの一機能としてリリースされるかも知れません。

4. まとめ

現在は、MySQL・Postgresqlを利用したければAWSのAuroraかRDS、SQL Serverを利用したければAzureでしょうか。
ただ各クラウドのプレビュー・ベータ提供状況を見ていると、そのうち機能差は無くなるように思えます。

続きを読む