Logic Dice このページをアンテナに追加

2016-06-02

Amazon Simple Workflow ServicesをAWSCLIで使ってみて理解する

ちょうどAWS上でワークフローが必要になるような業務を実装することになったので、アーキテクチャを考えてみました。

もちろん、それらのワークフローの実施が冗長化されており、同時にスケールすることも前提で。

…と、意気込んで以下のコンセプトを元にしたアーキテクチャ図を考えていました。

  • 1つの処理の流れの状態を常に持っている (どこまで進んだか、それぞれ中間でどのような結果となったか、など)
  • それぞれの処理はLambdaで実装し、上記で示した状態を受け取る
  • Lambdaも処理全体をコントロールするControllerと、具体的なサブタスクを実行するSubfunctionsに分ける
  • 各Lambda Function自体は入力のみに依存した処理を実行できるのでステートレスを保てる
  • Lambda Functionの終了時に後続作業がある場合は、入力を変更したメッセージを用意したSQSに投げる。 この時、必要であればDelayオプションを使って実行を遅延させる
  • 処理途中のSQSのメッセージキューはポーリングされており、適切なタイミングで拾われ、再度Lambdaを実行させる (Lambda側では状態に基づいた処理を行い、何も無くなったら処理を終わらせる)

理論的には実装できなくもないですが、問題も数多く有ります。

例えば、次のようなものです。

  • SQSのメッセージは2度以上取得される可能性があるため、実行を1度のみに保証する場合の作りこみが大変
  • SQSの順序保証が無いため、実行順序が重要な場合は使いづらい
  • 1つの処理の流れが何処まで進んだかの可視化が難しい

どうしたものか…と悩んでいたのですが、前々からやけに概念を理解するのが難しそうで避けていたAmazon Simple Workflow Services (SWF) とほとんどそっくりな考え方であったのと、SQSの性質に基づく問題が解決できるということもあったので、本腰を入れてSWFを調べてみることにしました。

日本語の資料が少ないのもあれですが、SWFを紹介している資料は多くの場合はAWS Flow FrameworkというSWFを実装するためのライブラリを利用してSWFを紹介しているものが多かったです。

ただ、これだと個人的には非常に理解しづらかったので、AWSCLIから(ほぼ)APIレベルで機能を使ってみつつ、SWFの仕組みの理解をしようと試みました。

結果としては、APIだけを使ってみると非常にシンプルなできになっている上に、非常に汎用的で使い道も数多くありそう、という結論が得られました。

続きを読む

2016-05-02

Amazon Cognito User Pools のAuthorize APIを結局解読した

1度挫折したんですが、Node.js側からせめてサーバーサイドでも認証できました。

具体的なコードを(ほとんどコピーですが)以下のプロジェクトに上げました。

https://github.com/a-hisame/cognito-userpools-example

cognito-idpのGetAuthenticationDetailsとAuthenticateをそれぞれ定義して使っています。

詳しくはプロジェクト内の feature-list-ja.md あたりを読んでみてください。

これで、

  • ユーザの生成(+確認用コード付き)
  • ユーザのパスワードなどの再発行
  • ユーザの管理
  • アクティブユーザの閲覧
  • ユーザに紐付く一時IAMの発行

などがAWS Cognitoだけで完結できるようになりました *1。 すげぇ。

*1:厳密にはIAM RoleとかSTSとかをCognitoが使ってるけど、表面的には

2016-04-25

Amazon Cognito User Pools をPythonから利用しようとして挫折した

先週のAWS Summit in シカゴで発表された AWS Cognito User Pools ですが、モバイルウェブ画面からだけではなくて、サーバー側の認証機構として使えないかをPythonとAWSCLIで色々試行錯誤して失敗したので記録しておきます。

原理的にはできなくもないんですが、現在提供されているソースコードを使わない場合は生のAPIを読み解いてゴリゴリ実装する必要がありそうなので、自分は調査を打ち切りました。

現在専用の命令が提供されていない言語でも同じことが言えると思います。

できたこと

そもそものUserPoolsの設定は こちらの記事を参照 して設定しました。

1点注意事項として、Appを設定するときに"Generate Client Secret"の設定がデフォルトでONになっていますが、

JavaScriptや他のAPIコールの場合まだSecret ONの場合に対応していないようなのでOFFにしましょう。

この設定を行った後に、

までは特に問題なく実施できました。

できなかったこと

要は、作ったユーザとパスワードログインして、そのログインセッションIDを取得するオペレーションがやりたかったのです。

アプリケーションを作る場合、ユーザやパスワードアプリケーションDB内に持つことが多いですが、

それを代替するサービスとしてCognito UserPoolsが使えないかを検証していました。

原因

感想

  • いろんなAPIの呼び出し(例えばDeleteUser)に"AccessToken"を要求するくせに、それを取得する方法がない*1API...
  • 最初はnode.js読んでたけれど、JS力が足りなくて読めなかった...
  • AWSのPublicBetaってAPIを含めて全て公開されるものと思ってたんですが、どうやって使えばいいかだけしか公開されないときもあるんですね...

追記

node.jsですがリベンジしました - http://d.hatena.ne.jp/a-hisame/20160502/1462194635

*1:多分他のOpenIDとかの場合はそれが使えるんだろうけど

2016-02-12

AWS LambdaでVPC内のPrivateインスタンスにSSH接続する

http://aws.typepad.com/aws_japan/2016/02/access-resources-in-a-vpc-from-your-lambda-functions.html

昨年のAWS re:Inventで対応が発表されていましたが、とうとうAWS LambdaからVPCリソースにアクセスが出来るようになったので何が出来るのか眺めていました。

そうすると、どうやら「VPC内にNetworkInterfaceを作ることでVPCリソースにアクセスする」とのことだったので、もしかするとこれはLambdaの起動インスタンスからフリーダムな事ができるのではないかと思い、ちょっと検証してみることにしました。


やりたかったこと

LambdaからVPC内の(インターネットに公開していないインスタンスに)SSH接続して、その内部の情報を拾う/書き換えることが出来ないかを検証する。

今まで、AWS上のAPIで出来ることは基本的に "インスタンスの外側" を制御するものがほとんどであり、"インスタンスの内側" の制御を行う仕組みは基本的に存在していませんでした。

EC2のUserDataを使うなどすれば一時的には利用できますが、Launch時のみに限定されます。

これまでの機能だけでpush型で外部から定期的に制御を行うモデルを構築しようとすると、その制御システムを冗長化する必要があって料金が高くついたり、ある時間のジョブをどれか1つのマシンだけが実行する制御が必要になる場合があるなど、技術的・コスト的にペイさせるのが難しいという問題がありました。 一応、中身を操作するサービスとして CodeDeployがあるにはありましたが、アレはあれでエージェントのインストールが必要になるなど、そこそこ準備側のコストが高いように感じてました。

しかし、AWS Lambda + スケジューラ (or 外部トリガ)によるSSH通信によるプッシュ処理が仮に可能になると、"インスタンスの内側" の制御を堅牢*1に、安価*2実行できる可能性が高まります。


ぱぱっと検証

私がPythonに慣れているので、PythonでのAWS Lambda Functionを組みました。

その中で、最初は subprocess.call('/bin/ping -c 1 [target]') のように pingコマンドを使おうとしたのですが、Lambdaの上では /bin/ 以下にすらアクセス権限がありませんでした (当たり前といえば当たり前ですが)

そこで、Pythonコードをアップロードするときに周辺のライブラリアップロードできることを利用して、ssh接続を行うfabric (と、内部で利用しているライブラリ群) をアップロードして使うことにしました。


ハマったところ

使うモジュール群は "pip install fabric -t ." でローカルに落としてこれるのですが、何故か "pycrypto" だけがこのコマンドでやっても落ちてこない。 明示しても /tmp 以下にファイルが存在しないと言われて失敗する。

これは "sudo pip install pycrypto" でやると成功するので、そこでインストールした先である "/usr/local/lib64/python2.7/site-packages/" 以下から持ってくることで解決しました。

あと、Lambdaモジュールが利用するNetworkInterfaceの存在するsubnet内に例えIGW向けのルートが張ってあってもインターネットに出ることができません*3。 言い換えると、全てのAWS APIが使えなくなります。

これを避けるためにManaged NATを立てるか、S3だけであればS3 Endpointへのルーティングが必要となります。


やってみた

2回目以降エラーログが出力されたりする問題はありますが、とりあえずAWS LambdaのTestでssh接続を行って、リモートのコマンドを実行することには成功しました。

これは中々出来ることが広がるアップデートだと思います。 この使用方法が想定されていたかどうかは知りませんが。


ソースコード

簡単なサンプルを以下のpublicプロジェクトにアップロードしました。

自由にご参照/ご利用下さい。

https://github.com/a-hisame/ssh-connection-with-aws-lambda

*1:Lambdaその物が冗長化されている

*2:実際の実行時間のみ課金、待機時間の課金は不要

*3:Public IPを付与しないため当然といえば当然。

2015-12-28

AWS LambdaのPython版をいまさらながら使ってみた

ので、導入の時に迷った点を残しておく。

  • pythonで用意するエントリポイントのファイル名や関数名は自由 (ただし、Configuration#Handlerで"ファイル名.エントリポイント関数名" で指定する必要がある)
  • エントリポイントの関数の第一引数は基本dict形式でイベントが飛んでくる。 第二引数はLambdaの実行メタ情報などを記録したContext
  • エントリポイントが返すreturnの値は特に利用しない (実行テストの結果に使われるため、どのような状態で終わったかの文字列を書いておいても良さそう)
  • AWSリソースを扱うときのライブラリはboto3がデフォルトで使える
  • ログ関連のIAM Roleを作って、それに自分が使うサービスのパーミッションを与えると楽
  • 外部ライブラリを使いたければzipアップロードするときに中に含める。 "pip install hoge -t ."
  • 固定の設定ファイルなどもzip内においておけば、読み込むことは可能
  • cronの設定はUNIXとは異なる形式で5個ではない。

実際に使ってみると、zipファイル圧縮でアップロードする、boto3が使えるといった点でかなりそのままローカルで書いたPythonをそのまま使えたイメージ。

2015-09-01

Amazon Linux上のOpenJDKをアップデートするとTimeZoneのデフォルトがおかしくなったというお話

はじめに

Amazon Linuxで動作するJavaのWebアプリケーションを動かしていたんだけれど、アップデートをするとどうにもログの出力日付がおかしい。

ということで色々調査してみてた結果、以下のTimeZoneのデフォルト値(取得方法は下記のソースコードを参照)が openjdk-7u79 と openjdk-7u85とで異なる結果を返していることが問題の根源である模様。

なお、コマンドの date で取得できる結果は JSTになっていることを確認済み。

(追記: Amazon Linuxglibcが更新されると /etc/localtime も合わせて更新されてしまい、システムの日付がJSTに設定されていたにも関わらずUTCに戻ってしまう現象もありますが、この問題の原因はこれとは異なり、純粋にopenjdkのみをupdateした場合に生じる問題です)

import java.util.*;

public class Test {
  public static void main(String[] args) {
    System.out.println(TimeZone.getDefault());
  }
}
// openjdk 7u79
sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null]

// openjdk 7u85
sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]

バグなのかどうなのか良くわからないので、一応原因の調査をしてみることに

結論

デフォルト取得の仕様が変わっている模様。

Amazon LinuxでTimeZoneを変更する方法については date の結果だけを変更する方法*1ブログでは紹介されていることが多く、今回はそれしか実行していなかったが、Amazon Linuxのガイドをちゃんとみてみると /etc/sysconfig/clock の値を修正することがきちんと書いてある。

http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/set-time.html

この設定のZONEの値を "Asia/Tokyo" に修正してやれば、きちんとZoneInfoの値としてAsia/Tokyoを返すようになった。


おまけ

CentOS7/RHEL7 から時刻変更のためのコマンド timedatectl というコマンドが追加されている。

今回の検証中に問題がJVMビルドなのかOSに依存するのか分からなかったため、RHEL7をローンチして調べていた。

その際にも検証のため /etc/localtime のみをJSTに更新していたが、このコマンドを知って現在の情報を表示してみると、全く関係ないTimezoneの値が表示されるにも関わらず、その時刻修正だけが(JST +0900)となっていた。

そのため、/etc/localtime のみではなく別のところでTimezoneの情報を保持していることが分かり、問題の原因らしきものと解決方法を発見することができた。

なお、この時に調査してみた限り、openjdk-8u51でも /etc/localtime に依存しないTimeZoneを返していたため、今後のバージョンアップLinuxの時刻設定手法を表面的に実施している場合、アプリケーション側にしっぺ返しが来るかもしれません。

*1:/etc/localtimeを変更する

2015-06-01

Eclipse BIRTのレンダリング時にReport Parametersによって読み込む固定ファイルを変更する

シナリオ

  • レポート生成ツールとしてEclipse BIRTを利用している
  • 多数のCSVファイルが存在する
  • BIRT Serverへのリクエスト時に引き渡す値によって異なるCSVファイルを読み込んで出力したい

CSVを読み込むには

以下、BIRT用のEclipse Designerを利用していることが前提条件です。

今回はBIRT 4.3.1で確認。

  • Data Explorer > Data Sources > New Data Source
  • Flat File Data Sourceを選択し、このデータソース名の名前を入れる(例: summary)
  • Define Folder or a File URI で何も入力せず*1に Test Connection > Finish
  • Data Sourcesに追加されたデータソースをEditで開き、Property Bindingを選択
  • この中の File URI にはJavaScriptを利用した動的URIが記載できるので、それに従って記載する。

具体例として、year, month, dayを受け取って、"data/[year]/[month]/[day].csv" *2 を開く場合は以下のようになる。

なお、相対パスでファイルを記述する場合はContextRootからの相対パスになります。

大抵の場合、/(Tomcatなどのインストールパス)/webapps/birt/ がContextRootとなるので、この場合はbirtの下にdataという名前のシンボリックリンクを張っておけば便利に利用できるでしょう。


'data/' + param['year'] + '/' + param['month'] + '/' + param['day'] + '.csv'

XML Sourceで見るとこんな感じ。

        <structure>
            <property name="name">URI</property>
            <property name="id">10</property>
            <expression name="value" type="javascript">'data/' + param['year'] + '/' + param['month'] + '/' + param['day'] + '.csv'</expression>
        </structure>

最初のWizardでできると思ってかなり調査に時間を使ってしまった…


おまけ

*1:後述の入力があると、この入力は無視される模様

*2:ここでは 0-padding無しの入力を考慮していないので、そのチェックが必要な場合はJavaScriptを使って記述してください