フレクトのクラウドblog re:newal

http://blog.flect.co.jp/cloud/からさらに引っ越しています

HerokuでTensorFlowのAPIをホストした話

この記事は、Heroku Advent Calendar 201920日目の記事です。

19日目は@mttrsさんさんによる「Heroku Changelog 2019」でした。 21日目は@sizukutamagoさんです。

こんにちは、技術開発室の藤野です。 今回はHeroku上でTensorFlowの推論を行うWeb APIを作った話についてです。

はじめに

今回の話のあらましは、TensorFlowでディープラーニングのモデルを作ったので、Web APIとして使えるようにしたい、というものです。 APIはリクエストに対して推論結果を返すシンプルなもので、なるべく早く構築しデプロイや運用の手間は省きたいと思っていました。

これに対して、Herokuは下記の利点があります。

  • インフラ、ミドルウェアが用意されており、デプロイが容易
  • 運用のための仕組みがあらかじめ提供されている
  • CI/CDの機能を利用できる

一方で、ディープラーニングアルゴリズムGPU等を使って高速化することが一般的だと思います。 Herokuではこのようなアクセラレータは利用できず、CPUのみで推論処理を行うことになります。

そこで、今回はこのような推論処理を含むAPIをHeroku上で動かしたときのパフォーマンスはどのくらいなのか?を主眼に見ていきたいと思います。

続きを読む

CariotはNew RelicとAmazon EventBridgeにトラスト構想の夢を見る

こんにちは、遠藤@Cariot事業部です。
今回はCariot SREチームの一員として投稿します。

はじめに

フレクトが提供している車のIoTサービス「Cariot (Car+IoT)」では、先日New Relic社と共同でプレスリリースを発表し、サービスの性能に関する透明性を高めるべく、取り組みをはじめました。

www.flect.co.jp

わたしの所属するCariot事業部 製品開発部でも、上記、大きなビジョンの実現を目指しながら、少しずつできることを進めています。
今回は、現在進行中の取り組みについて紹介します。

続きを読む

Auth0の話

こんにちは。としやさんじゃない方の齋藤です。Advent Calendar の季節がやってきました。

qiita.com

HerokuのDynoやアドオンの構成は、定番がありつつもプロジェクトごとの微妙な色彩の違いにフィットさせる必要があり、いつも座組みに頭を悩ませるところでありますが、個人的にはカードゲームのデッキ構築みたいなものだと思っています。Creditぎりぎりまで強いカードを少しでも多く入れたい。

ずっと前から存在は知っていたAuth0にようやく触れる機会が取れたので、検討している方に少しでもヒントになればと思ってAuth0の紹介をしてみたいと思います。

Auth0とは

f:id:shns:20191217224440p:plain https://auth0.com/jp/

IDaaS/CIAMとして最もポピュラーなサービスの一つで、たとえば認証・認可、セキュリティにまつわる 面倒な実装をせず、強力な認証基盤をすぐさま自身のアプリに取り込むことができます。

Documentationにある Overview を見ると、

Auth0 provides authentication and authorization as a service.(Auth0は認証と認可をサービスとして提供します)

https://auth0.com/docs/getting-started/overview

とありますが、のみならず

  • ログイン履歴をダッシュボードで見れたり
  • 会員機能にまつわるメール機能は一通り備えていたり
  • 既存の認証基盤とつなげやすかったり

といったように、使いやすさや拡張性、開発者目線の機能が豊富です。

特徴

特にSDK、サンプルコード、対応するソーシャルログインなど、品揃えが圧倒的。

f:id:shns:20191217224856p:plain f:id:shns:20191217231118p:plain

それと今年はLINEやSign in with Appleが標準で提供されたりしました。

auth0.com auth0.com

また、個人的な印象としては、管理者向けのダッシュボードがとにかく始めやすいように作られているので、従来のエンタープライズ系SSO製品と比べて、0を1にする部分の苦しみから開発者が圧倒的に解き放たれる感があります。OpenAMの複雑な管理者画面の遷移の仕方を覚えたり、ちょっと設定を変えたいだけなのに/var/lib/tomcat/webapps界隈に足を運ばないと行けなかったり・・といった手間はないので大事なコードに集中できる感は圧倒的に勝っています。

引き算の美学

いつもシステム構成の下敷きとして定期的に見直しているpostに以下があります。

https://engineering.videoblocks.com/web-architecture-101-a3224e126947

上のポンチ絵には出てこないのですが、初学を終えてこの絵が分かる頃に ちょうど認証認可の仕組みの理解が必要になるタイミングがあると思うのですよね。

Javaでいうと、スレッドプログラミングができたら中級者みたいなそんな感じかな・・(個人の意見です)

現代のWebアプリにはログイン機能は当たり前のようにありますが、自前で作る場合は思ったよりも機能が爆発したり、OpenAMなどのオープンソースを使うにしても非機能要件を考慮した結果それなりにインフラ面が大掛かりになってしまったりと、トータルで見たときに手間が大きくなってしまうことがあります。

一方で、ID基盤が提供するところのログインというユーザ体験そのものとしては、

  • かなりの頻度で通る(実装次第ですが)
  • 複数経路があり、エントリーポイントが多い
  • 比較的早い回遊タイミングで到達されることが多い

といった点があり、最初から入れるにはそこそこ大玉で、かといって後から入れるには 全体への基盤改修リスクが大きい、といった結果として跳ね返ってきます。

コンバージョンを継続的に改善したいB2C系サービスや、 顧客情報は集めたいけど顧客のストレスを増やしたくないと考えているサービスにとって、 導線検討やセキュリティ面の考慮というのは避けて通れない。

こういった手間がかかるが逃げられない、といったジレンマが 強力な標準提供サービス(SDK、テンプレートコードなど)によって 抽象化ないしスキップできてしまうのがAuth0の良いところかなと。

CRMとの親和性

CIAMとして見て、やはりCRMとの親和性の高さがポイントの1つかなと思っています。

ユースケースとしては、認証・認可の仕組みとして利用しつつ、 Auth0ログイン時などにログをイベントとして受け取れるので、KafkaやAmazon Eventbridgeを配管して function codeやバッチ処理からCRM・MAに取り込み、ネイティブにプッシュ配信、など。

auth0.com

Log driven なので、セキュリティオートメーションでも重宝しますね。

あるいはCRM上のビジネスユーザは別のテナントなりアプリケーションで SAML FederatedにSSOしてWebアプリ側にも行き来しやすくする、といったアプローチも取れます。

Auth0の良いところは、Rulesを使って認証時の様々なフック処理を書けるところです。 こんな感じで。

f:id:shns:20191217225549p:plain

管理者のコンソールでコード片のデバッグが出来るのは地味に強い。それはそれとして、Auth0標準のカスタムデータベースとソーシャルログインのユーザをマージする前提でWebアプリを組むときや、外のサービスを呼びつつid_tokenに必要な情報を詰め込みたいとかログインのうち何回かは外部DBからの最新情報を取ってきてキャッシュしたいとか、色々なユースケースで活躍します。

主要なイベントについてはテンプレートがあるので、流用開発ベースで進めることができ、どれくらい手間がかかるのかの見立てが立てやすいところがポイント。

収集・集約・アプローチのタッチポイントをAddonインストール一発で作れるのは非常にありがたいところなのではないでしょうか。

Add-onsとしての利用

今年、yonyonさんとSWTTでHerokuの話をしたとき※には認証周りの話題はしなかったのですが、当然Herokuのアプリで認証込みでサービスを作るときにはAuth0が第一の選択肢になってきます。

見積から開発・運用まで!Herokuの基本とTips - Qiita

当たり前ですがHerokuのユーザで各ステージのアプリにシングルサインオンできるし、テナントのユーザ権限もHerokuユーザに付与できるので、ちょっと管理者ユーザとして他の人に見てほしいときに、Collaboratorとして招待できますし。

Add-onプランとしては以下があります。

https://elements.heroku.com/addons/auth0

  • Free
  • Silver
  • Gold
  • Enterprise B2C

AD連携やマルチデータソース戦略を取るわけでなければ、意外とキャップは少なくFreeで出来ることが多いです。なのでステージ構成としては、development,stagingはFree/Silverで、productionから GoldもしくはB2C Enterpriseという構成などでも良いかなと思います。

Planを考えるときの閾値については、MAU(Monthly Active Users)がベースとなります。つまり、月1回でもログインすればそのユーザは1カウントとして計算され、プランごとの上限値はそのMAUで決まります。

f:id:shns:20191217230432p:plain

この例で言うと、Enterprise B2C の 10,000 Usersに対して先月は2,500ユーザ。まだまだ余裕があります。

一応 Dashboardのトップでもログインユーザのサマリが確認できるのですが、各月のアクティブユーザについては以下から当月のQuotaを参照できます。

User Dropdown > Account Usage > Home > Quota Utilization > Regular Active Users

なお、Auth0でユーザ登録する(Database User)とソーシャルログインを併用しており、両者をマージルールで1ユーザとしてマージした場合には、どちらのログインを利用してもMAUは1カウントになります。逆になると別カウントなので、マージ可否は要件として決めておいた方が良いです。

リミットを超えてきそうな場合にはプランの引き上げ(再検討)が必要になりますが、最初からGold以上で行くなら最初にキャパシティ設計をしっかりやっておきます。だいたいHerokuはスモールスタートもしくは3-5年くらいを目安に非機能設計しておくのが多いと思いますが、予想よりサービスがうまくいくと、それでも不足するケースもあります。

しかし、重要なのはビジネス成功に伴ってPlanを拡張するというポジティブな拡張。

とはいえ、RFMっぽくlast_loginが一定以上な「休眠ユーザ向け」にキャンペーンを打って月のログインがスパイクするようなケースなど、予期しきれないユーザ激増などもあると思うので、適度に流量と容量は見ておいた方が良いです。

モニタリングの選択肢としては、シンプルにダッシュボードを見るようにするのが手っ取り早いですが、

  • Log->Amazon Eventbridge IntegrationからSQSに貯めて、一定量キューが溜まったらMAU計算して通知するfunctionを書く
  • 1-off Dyno + Management APIで定期的に以下略

あたりができると良いと思います。思いついただけで作ったわけではないけど・・・

Freeで出来ることが非常に多いアドオンなので、とりあえず使ってみてから考えればよいかなと思います。

小西さんの書いた弊社過去ブログやサンプルサイトにも載っているので、ご参考までに。

ちょっとだけ気になるところ

ドキュメントやリファレンスはオープンかつそれなりに充実しているものの、使ってみて多少癖のあるところや認証基盤ならではの沼っぽいハマり方をするケースも時々あります。

よくあるトラブルシューティングやベストプラクティスがわかるようなナレッジベースが充実するとより良いな・・・と思ってます。

とはいえこれだけの機能をそれなりに小ささでも使うことができるのはやっぱりサービスとしての力があるのだと思いますし、こまめなアップデートも結構好きなので、たぶん来年も追っかけていくとは思います。それでは。

Image processing algorithm - Index -

技術開発室の馮 志聖(マイク)です。

I will list some image processing algorithm. And I will introduce detail from next time. It is important for understand how computer know the real world. Computer vision include acquiring, processing, analyzing and understanding digital images.

Edge detection

  • Canny
    f:id:fengchihsheng:20191213145831p:plain
    Canny
  • Sobel
    f:id:fengchihsheng:20191213145906p:plain
    Sobel X
    f:id:fengchihsheng:20191213145926p:plain
    Sobel Y
    f:id:fengchihsheng:20191213145948p:plain
    Sobel
  • Prewitt
    f:id:fengchihsheng:20191213150011p:plain
    Prewitt
  • Laplacian
    f:id:fengchihsheng:20191213150037p:plain
    Laplacian
  • Laplacian of Gaussian
    f:id:fengchihsheng:20191213150057p:plain
    Laplacian of Gaussian

Edge detection (RGB)

f:id:fengchihsheng:20191216101605p:plain
Edge detection (RGB)
f:id:fengchihsheng:20191216101626p:plain
Edge detection (RGB)
f:id:fengchihsheng:20191216101646p:plain
Edge detection (RGB)

Background subtraction

  • Differential
    f:id:fengchihsheng:20191213150431p:plain
    Differential
  • Otsu
    f:id:fengchihsheng:20191213150453p:plain
    Otsu
  • Binary
    f:id:fengchihsheng:20191213150512p:plain
    Binary
  • Adaptive Mean Thresholding
    f:id:fengchihsheng:20191213150532p:plain
    Adaptive Mean Thresholding
  • Adaptive Gaussian Thresholding
    f:id:fengchihsheng:20191213150546p:plain
    Adaptive Gaussian Thresholding

Corner detection

  • Harris operator
    f:id:fengchihsheng:20191213154003p:plain
    Harris operator
  • Shi and Tomasi
    f:id:fengchihsheng:20191213154026p:plain
    Shi and Tomasi
  • Features from accelerated segment test (FAST)
    f:id:fengchihsheng:20191213163337p:plain
    Features from accelerated segment test (FAST)

Blob detection

  • Laplacian of Gaussian (LoG)
    f:id:fengchihsheng:20191213173044p:plain
    Laplacian of Gaussian (LoG)
  • Difference of Gaussians (DoG)
    f:id:fengchihsheng:20191213173106p:plain
    Difference of Gaussians (DoG)
  • Determinant of Hessian (DoH)
    f:id:fengchihsheng:20191213173125p:plain
    Determinant of Hessian (DoH)
  • Maximally stable extremal regions (MSER)
    f:id:fengchihsheng:20191213180130p:plain
    Maximally stable extremal regions (MSER)
  • Optical character recognition (OCR)
    f:id:fengchihsheng:20191213182619p:plain
    Optical character recognition (OCR)

Ridge detection

  • Hessian matrix
    f:id:fengchihsheng:20191216095911p:plain
    Hessian matrix

Hough transform

  • Generalized Hough transform
    f:id:fengchihsheng:20191216113113p:plain
    Generalized Hough transform
  • Hough transform
    f:id:fengchihsheng:20191216115716p:plain
    Hough transform
  • Line Segment Detector (LSD)
    f:id:fengchihsheng:20191216120038p:plain
    Line Segment Detector (LSD)
  • Fast Line Detector (FLD)
    f:id:fengchihsheng:20191216120057p:plain
    Fast Line Detector (FLD)

Structure tensor

  • Gradient Structure tensor (GST)
    f:id:fengchihsheng:20191216145635p:plain
    Gradient Structure tensor (GST)

Feature description

  • Oriented FAST and rotated BRIEF (ORB)
    f:id:fengchihsheng:20191217105404p:plain
    Oriented FAST and rotated BRIEF (ORB)
  • AgastFeatureDetector
    f:id:fengchihsheng:20191217112104p:plain
    AgastFeatureDetector
  • Features from accelerated segment test (FAST)
    f:id:fengchihsheng:20191217112133p:plain
    Features from accelerated segment test (FAST)
  • Maximally stable extremal regions (MSER)
    f:id:fengchihsheng:20191217112200p:plain
    Maximally stable extremal regions (MSER)
  • KAZE Features
    f:id:fengchihsheng:20191217112231p:plain
    KAZE Features
  • Accelerated-Kaze Features (A-Kaze)
    f:id:fengchihsheng:20191217105341p:plain
    Accelerated-Kaze Features (A-Kaze)
  • Binary Robust Invariant Scalable Keypoints (BRISK)
    f:id:fengchihsheng:20191217105322p:plain
    Binary Robust Invariant Scalable Keypoints (BRISK)
  • Scale-invariant feature transform (SIFT)
    f:id:fengchihsheng:20191217105256p:plain
    Scale-invariant feature transform (SIFT)
  • Speeded up robust features (SURF)
    f:id:fengchihsheng:20191217112304p:plain
    Speeded up robust features (SURF)
  • Histogram of oriented gradients (HOG)
    f:id:fengchihsheng:20191216151117p:plain
    Histogram of oriented gradients (HOG)
  • Local Binary Pattern (LBP)
    f:id:fengchihsheng:20191217132849p:plain
    Local Binary Pattern (LBP)
  • Grey Level Co-occurrence Matrices (GLCM)
    f:id:fengchihsheng:20191216150811p:plain
    GLCM black space

Signal processing

  • Fast Fourier transform (FFT)
    f:id:fengchihsheng:20191217140122p:plain
    Fast Fourier transform (FFT) Magnitude Spectrum
    f:id:fengchihsheng:20191217140431p:plain
    High Pass Filtering
  • Discrete Fourier transform (DFT)
    f:id:fengchihsheng:20191217140451p:plain
    Discrete Fourier transform (DFT) Magnitude Spectrum
    f:id:fengchihsheng:20191217140755p:plain
    Low Pass Filtering

Shape detection

  • Morphological Snakes
    f:id:fengchihsheng:20191217132032p:plain
    Morphological Active Contours without Edges (MorphACWE)
    f:id:fengchihsheng:20191217132107p:plain
    Morphological Geodesic Active Contours (MorphGAC)

最後に

I will update more and more algorithm in this page. If I add new blog for detail algorithm, I will also update here too.

Face Recognition on JavaScript

技術開発室の馮 志聖(マイク)です。

前回の投稿。 cloud.flect.co.jp

I will introduce face recognition on javascript. And some algorithm for feature detection. Recently,JavaScript engines and browsers have become more powerful that building full-blown applications in JavaScript is not only feasible, but increasingly popular.

Feature detection

In computer vision and image processing feature detection includes methods for computing abstractions of image information and making local decisions at every image point whether there is an image feature of a given type at that point or not. The resulting features will be subsets of the image domain, often in the form of isolated points, continuous curves or connected regions.

Machine learning

  • Neural Structured Learning (NSL)
    f:id:fengchihsheng:20191216142248p:plain
    Neural Structured Learning (NSL)

https://www.tensorflow.org/neural_structured_learning

Google Tensorflow Blog have some content about "Neural Structured Learning in TensorFlow".

https://medium.com/tensorflow/introducing-neural-structured-learning-in-tensorflow-5a802efd7afd

Visualizing ML training using TensorFlow.js and Baseball data.

observablehq.com

https://medium.com/tensorflow/predicting-balls-and-strikes-using-tensorflow-js-2acf1d7a447c

Face Detection & Recognition

Check this video from Youtube and see how Haar working.

www.youtube.com

face-api.js

Face-api.js is a JavaScript API for face detection and face recognition in the browser implemented on top of the tensorflow.js core API. It implements a series of convolutional neural networks (CNNs), optimized for the web and for mobile devices. face-api.js implements the models SSD Mobilenet V1, Tiny Face Detector, and the experimental MTCNN.

f:id:fengchihsheng:20191217143524j:plain
Face future
https://www.sciencedirect.com/science/article/pii/S1077314215000727

f:id:fengchihsheng:20191217143806p:plain
Facial Landmark
https://www.semanticscholar.org/paper/Facial-Landmark-Tracking-by-Tree-Based-Deformable-Uric%C3%A1r-Franc/eb97fabbe07999fe799f352dedf62acc7a65b6f0

DEMO

  • Detect Feeling
    f:id:fengchihsheng:20191217150424p:plain
    Neutral
    f:id:fengchihsheng:20191217150455p:plain
    Happy
    f:id:fengchihsheng:20191217150518p:plain
    Sad
    f:id:fengchihsheng:20191217150554p:plain
    Surprised
  • Verification
    f:id:fengchihsheng:20191217150627p:plain
    Verified
    f:id:fengchihsheng:20191217150809p:plain
    Unverified
  • Video Human emotions detection


Human emotions detection

Web verification base on face recognition


Web verification base on face recognition

Some idea for using case

f:id:fengchihsheng:20191212163919p:plain
Realtime face verification on Website

最後に

Face recognition can use on many verification case.

Reference

Feature detection
Face Recognition
JS library

AWS Step Function をユニットテストするには

エンジニアの佐藤です。こんにちは。今回は「AWS Step Function のユニットテスト」を構想し、実装までに直面した課題と私が考えた解決方法についてお話ししたいと思います。

AWS Step Functionとは

公式ページに書かれてますが、 Step Functions は様々なサービスをつなげて「サーバーレス・ワークフロー」を編成する仕掛けです。 AWS には古来から様々なワークフロー支援サービスがありますが、その中では最も新しいものです。

ことの始まり

筆者はある日、とある業務システムをどうやって実装したものかと考えていました。AWSでやってくれという話でしたので、AWSの各種サービスを比較検討し、最終的にStep Functionsを選択しました。重視したポイントは以下のようなものです。

  1. 拡張性が高い。
  2. 高可用性設計。
  3. ワークフローの進捗が視覚的に確認できる。
  4. ワークフロー定義が読みやすい。

今回の業務システムには短時間で完了する軽いタスクと、リソースも時間も必要な重いタスクの2種類があり、筆者は前者をLambda Functionに、後者をECS(Fargate)で実行することにしました。

Step Functionのユニットテスト・・・って?

ところで昨今のシステム開発は、テストコードを記述してユニットテストを行うのが一般的です。筆者はこのStep Functionsで作成されるワークフロー設定(ステートマシン)についてのユニットテストを構想しました。ところが、考えていくうちに、これが結構深いタスクであることに、徐々に気がついてきました。一般的な話ですが、システムを人間の体に例えると、手足に相当する機能のユニットテストについては、準備作業はそれほど難しくありません。一方で中枢的な機能のテストのための準備作業は大きくなりがちです。動作が確実な仮の末端部品を接続してあげる必要があるからです。「ワークフロー管理」機能は、この観点から言うとシステムの最も中枢的な部分であり、その単体テストのためには仮の末端部品の接続が必要ということになります。今回で言えば、Lambda FunctionとECSに仮のモジュールを入れてあげる必要がある。

Lambda FunctionとECSの仮のモジュール、と構想したところで、筆者は更に寒気を覚えました。よく考えてみるとこの2つ、機能コードを配置するまでの作業がそれなりに手間なのです。例えばLambda Functionの場合、以下のようなステップがあります。

  1. ライブラリを含めた機能コードのアーカイブ
  2. アーカイブのS3への配置
  3. IAMの設定
  4. Lambda Functionの設定

ECSの場合はこうなります。

  1. 機能コードを含むDockerコンテナのビルド
  2. ECRへコンテナを配置
  3. IAMの設定
  4. タスク定義の作成

この「Lambda Functionの設定」と「タスク定義の作成」には、環境変数などの設定も含まれます。テスト用の仮のモジュールなどと言いながら、一式全部必要なわけです。ステートマシンのテストをするために「システムの設計方針に従って各種の設定を発行・注入する仕掛け」の開発が必要になる・・・そう気がついた筆者は、とんでもないことを始めてしまったのではないかと、暗い気持ちになりました。中でも筆者が負担に感じたのがIAMです。Lambda FunctionもECSも機能コードの実行環境であり、実行の際に「インフラ的にできること(実行権限)」の範囲をIAMロールとして定義する必要があります。しかしこれがまた、慎重さと地道な努力を要する作業なのです。「とりあえずAdmin!」とやっつけたい気持ちはやまやまですが、これは悪魔の囁き。権限設定はミニマムからスタートしないと、権限のダイエットはとても大変なのです。

なお、Lambda FunctionとStep Functionsについては、AWSがローカルランタイムを配布していますが、今回は利用を見送りました。ローカル版のFargateはありませんし、ローカル環境固有の設定負担や互換性問題もあるでしょう。それなら最初から本物を使ったほうがいい、ユニットテスト用のAWSアカウントを短時間使うだけならそんなに経費もかからない、と判断しました。

各種設定の発行・注入する仕掛け・・・って?

で、その仕掛けをどうしたのかというと、この点は大変悩んだところでした。

筆者が最初に検討したのはAWS Cloud Formationでした。しかし調べていくと、これはこれで良くないところがあると思いました。まず引っかかったのが、テンプレートの入出力がいずれもリストとなっており、階層的にできない点です。また、最終的にJSONYAML形式のテンプレートにする必要があり、いくつか見て回った範囲ではこのテンプレートの調整負担が大きく見え、Colud Formationは見送りました。

次に検討したのがCDKでした。re:Invent18で発表されたもので、基本的にNodeJSやPythonなどのプログラム言語でCloud Formationのテンプレートを効率的に作成する仕掛けです。しかしこちらはStep Functionsの実装がまだプレビュー状態で、またステートマシンの定義方法が独特で良くないと思いました。また、ランダムな付加文字列の入ったリソース名となる点も、やり込むと散らかって良くないだろうなと思えました。

筆者が最終的に選択したのは、以下のような方式でした。

  1. まずプロジェクト名に連なる一連の名前体系を決め、これを環境変数で設定する。
  2. Python言語でdeploy.pyとundeploy.pyを記述し、ここからboto3 SDKを用いて直接リソースの作成と削除を行う。リソース定義は、これらのソースコードに記述する。
  3. ユニットテストはdeploy.py実行 -> テスト -> undeploy.py実行のフローで行う。

幸いなことに今回のシステムの開発言語はPythonでした。そしてAWSリソースの定義は、たいていJSONで書かれており、Python言語のdictionaryとほとんど同義(しかもコメントも挿入できる)。そして複数のリソース定義を体系的に作っていくにはプログラムフローが効率的で、デプロイ自体もその延長でSDKを呼び出してやってしまえば、小さくまとまらないかと目論んだわけです。

最初に「プロジェクト名に連なる一連の名前体系を決める」ですが、今回構想した「Step FunctionsステートマシンがLambda FunctionとECSコンテナを実行する」実装の場合、以下のようになりました。

export AWS_ACCOUNT_ID=`aws sts get-caller-identity --output json | jq -r -j ".Account"`
export AWS_DEFAULT_REGION=`aws configure get default.region | tr -d \\n`
export PROJECT_NAME=project01

export LAMBDA_FUNCTION_NAME="${PROJECT_NAME}_lambda"
export LAMBDA_FUNCTION_ARCHIVE_NAME="${FUNCTION_NAME}.zip"
export S3_LAMBDA_ARCHIVE_PATH="${PROJECT_NAME}/${FUNCTION_ARCHIVE_NAME}"
export BUCKET_NAME=s3_bucket_name12345s
export LAMBDA_POLICY_NAME="${PROJECT_NAME}LambdaPolicy"
export LAMBDA_ROLE_NAME="${PROJECT_NAME}LambdaRole"
export LAMBDA_FUNCTION_ARN="arn:aws:lambda:${AWS_DEFAULT_REGION}:${AWS_ACCOUNT_ID}:function:${LAMBDA_FUNCTION_NAME}:prd"

export ECR_REPOSITORY_NAME=$PROJECT_NAME
export ECS_CLUSTER_NAME=$PROJECT_NAME
export ECS_CONTAINER_TAG="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}:latest"
export ECS_TASK_ROLE_NAME="${PROJECT_NAME}TaskRole"
export ECS_TASK_POLICY_NAME="${PROJECT_NAME}TaskPolicy"
export ECS_TASK_EXECUTION_ROLE_NAME="${PROJECT_NAME}TaskExecutionRole"
export ECS_TASK_EXECUTION_POLICY_NAME="${PROJECT_NAME}TaskExecutionPolicy"
export ECS_TASK_DEFINITION_NAME="${PROJECT_NAME}Task"
export ECS_SUBNETS="subnet-12345678"

export SFN_NAME=$PROJECT_NAME
export SFN_ROLE_NAME="${PROJECT_NAME}SfnRole"
export SFN_POLICY_NAME="${PROJECT_NAME}SfnPolicy"

もういきなりお腹いっぱいな感じがありますが、ここに列挙したどれ一つ不要なものはありません。機能コードをサーバーレスに埋めていくとは、こういう手間を伴うものなのです。

デプロイコードとユニットテスト

そしてここからLambda Function、ECSコンテナ、そしてStep Functionsステートマシンをデプロイするコードを書いていくわけですが、ステートマシンをデプロイする部分をご紹介すると、以下のようになりました。

def deploy():
    print('deploying...')

    # (中略)

    containerDef = {
        'CLUSTER_ARN': f"arn:aws:ecs:{AWS_DEFAULT_REGION}:{AWS_ACCOUNT_ID}:cluster/{ECS_CLUSTER_NAME}",
        'TASK_DEFINITION_NAME': ECS_TASK_DEFINITION_NAME,
        'CONTAINER_TAG': ECS_CONTAINER_TAG,
        'ENVIRONMENTS': [
            {
                "Name":"TASK_TOKEN",
                "Value.$":"$$.Task.Token"
                },
            {
                "Name":"DATA",
                "Value.$":'$'
            }
        ]
    } # end of containerDef

    sm = get_steps(
        lambdaFunctionArn=LAMBDA_FUNCTION_ARN,
        containerDefinition=containerDef
    )

    sfnClient = boto3.client('stepfunctions')
    print('creating state machine...')
    # print(json.dumps(sm, indent=4))
    res = sfnClient.create_state_machine(
        name=SFN_NAME,
        definition=json.dumps(sm, indent=4),
        roleArn=f'arn:aws:iam::{AWS_ACCOUNT_ID}:role/{SFN_ROLE_NAME}',
        tags=[TAG_sl]
    )
# end of deploy

Step Functionsのステートマシン自体は、JSON文書として記述されます。ここへステートマシンから実行するLambda FunctionやECSのタスク情報を含めていくわけですが、これをステートマシン定義取得関数get_stepsの引数で指定するようにしておきました。 こうすれば、ユニットテストからこのデプロイコードが呼び出されたときに、本番とは違う仮のLambda FunctionやECSタスク情報を設定することができるだろうとの目論見です。

get_steps自体は以下のような実装になりました。

def get_steps(
    lambdaFunctionArn=None,
    containerDefinition=None
):
    SFN_NAME = os.environ['SFN_NAME']

    sm = {
        "Comment": SFN_NAME,
        "StartAt": "STEP1",
        "TimeoutSeconds": 300,
        "States": {
            "STEP1": {
                "Type": "Task",
                "Resource": lambdaFunctionArn,
                "TimeoutSeconds": 10,
                "InputPath": "$.input",
                "ResultPath": "$.res1",
                "OutputPath": "$",
                "Next": "STEP2",
                "Catch": [ {
                    "ErrorEquals": [ "States.ALL" ],
                    "Next": "FAILED"
                }]
            }, # end of STEP1
            "STEP2": {
                "Type": "Task",
                "Resource": "arn:aws:states:::ecs:runTask.waitForTaskToken",
                "TimeoutSeconds": 300,
                "Parameters": __create_ecs_parameters(containerDefinition),
                "InputPath": "$.res1",
                "ResultPath": "$.res2",
                "OutputPath": "$",
                "Next": "SUCCEEDED",
                "Catch": [ {
                    "ErrorEquals": [ "States.ALL" ],
                    "Next": "FAILED"
                }]
            }, # end of STEP2
            "SUCCEEDED": {
                "Type": "Succeed"
            },
            "FAILED": {
                "Type": "Fail"
            }
        } # end of States
    } # end of sm

    return sm
# end of get_steps

これはステートマシンのユニットテストの方法論の開発のためのテストのために仮組みしたものですが、これが下の図のようなステートマシンになります。

f:id:masashi-sato-flect:20191209141151p:plain
ステートマシン

予想外に苦労したのが __create_ecs_parameters の部分です。ステートマシンからECSを呼び出すにはタスク定義のARNを指定する必要がありますが、このタスク定義は更新の度に「project01Task:77」のように末尾の序数が加算されていき、この数字を指定する手段はありません。つまり目的のコンテナが指定されているタスク定義を(たいていは最新のタスク定義でしょうが)掘り出すしかないようなのです。これを掘り出してステートマシンのコンテナ定義を作成するのが、下記のような内容の __create_ecs_parameters 関数になります。

def __create_ecs_parameters(container_definitions):

    ECS_SUBNETS          = os.environ['ECS_SUBNETS'].split(',')
    TASK_DEFINITION_NAME = container_definitions['TASK_DEFINITION_NAME']
    CONTAINER_TAG        = container_definitions['CONTAINER_TAG']
    
    ecsClient = boto3.client('ecs')

    lTd = []
    nextToken = None
    while True:
        params = {
            'familyPrefix': TASK_DEFINITION_NAME,
            'status':       'ACTIVE',
            'sort':         'DESC'
        }
        if nextToken != None:
            params['nextToken'] = nextToken
        res = ecsClient.list_task_definitions(**params)

        if 'nextToken' in res:
            nextToken = res['nextToken']
        lTd.extend(res['taskDefinitionArns'])
        if nextToken is None:
            break
    # end of while (true)

    tdArn = ''
    for td in lTd:
        tdDesc = ecsClient.describe_task_definition(
            taskDefinition=td
        )
        image = tdDesc['taskDefinition']['containerDefinitions'][0]['image']
        # print(image)
        if image == CONTAINER_TAG:
            tdArn = td
            break
    # end of for (td)
    if tdArn == '':
        raise Exception(f'no task definition {TASK_DEFINITION_NAME} found for container {CONTAINER_TAG}')

    ret = {
        'LaunchType': 'FARGATE',
        'Cluster': container_definitions['CLUSTER_ARN'],
        'TaskDefinition': tdArn,
        'Overrides': {
            'ContainerOverrides': [{
                'Name': TASK_DEFINITION_NAME,
                'Environment': container_definitions['ENVIRONMENTS']
            }] # end of ContainerOverrides
        }, # end of Overrides
        'NetworkConfiguration': {
            'AwsvpcConfiguration': {
                'Subnets': ECS_SUBNETS,
                'AssignPublicIp': 'ENABLED'
            }
        } # end of NetrowkConfiguration
    }
    return ret
# end of __create_ecs_parameters

このような工夫を経て、ようやく当初の目的の「ステートマシンのユニットテスト」に到達しました。テストコード全体は以下のようになりました。

class TestSfn(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        deploy(test=True)
    # end of setUpClass

    @classmethod
    def tearDownClass(cls):
        undeploy()
    # end of tearDownClass   
    
    def test_sfn(self):
        
        AWS_ACCOUNT_ID     = os.environ['AWS_ACCOUNT_ID']
        AWS_DEFAULT_REGION = os.environ['AWS_DEFAULT_REGION']
        SFN_NAME           = os.environ['SFN_NAME']
        
        sfnClient = boto3.client('stepfunctions')
        
        execName = SFN_NAME + '_' + str(datetime.now().timestamp())
        input = {
            'input': {"Number1":10,"Number2":5}
        }
        
        res = sfnClient.start_execution(
            stateMachineArn=f'arn:aws:states:{AWS_DEFAULT_REGION}:{AWS_ACCOUNT_ID}:stateMachine:{SFN_NAME}',
            name=execName,
            input=json.dumps(input)
        )
        execArn = res['executionArn']
        res = {}
        while True:
            res = sfnClient.describe_execution(
                executionArn=execArn
            )
            status = res['status']
            if status != 'RUNNING':
                print(res)
                break
            time.sleep(5)
        # end of while True
        output = json.loads(res['output'])
        self.assertEqual(output['res2']['Difference'], 5)
    # end of test_sfn
# end of class TestClass01

このテストコードを実行することで、当初の目論見であったステートマシンのユニットテストが実行できるようになりました。テスト用のLambda Functionやコンテナと組み合わせれば、複雑な状態管理の作り込みと繰り返しの動作確認が可能になったと思います。

ふり返って、どうか

正直なところ、なかなか複雑な気持ちです。ステート管理・高可用性・柔軟かつ効率的なリソースアロケーションを目標として計画した目論見でしたが、結局CI/CDみたいなことをやることになってしまいました。Step Functionを本格的に業務活用しようとすると、自動デプロイの仕掛けを何かしら用意する必要があると感じました。設計目標はクリアできそうですが、けっこう大変でした。

特に設定が煩雑になったのはECSを呼び出す部分です。この点については以下のような支援機能がStep Functionに用意されていると、より開発作業が効率的になるのではないかと思いました。

  • ステートマシンとECSの入出力の高機能化。ステートマシンの入力値をJSONのまま受け渡しできるAPIの追加など。(現状では環境変数文字列にする必要がある。)
  • 個別のステートマシンからはECRのコンテナのみを指定して実行できるように簡略化。ステートマシン全体に既定のコンテナクラスタやタスク定義を設定する機能など。

最後までお読みいただきありがとうございました。

フレクトが行くAWS re:Invent 2019 (12/5)

CI事業部ウエダです。こんばんは

本日はFLECTとして参加するのは最終日となります。

先ほどre:Playが終わり、ホテルに戻ってきたの余韻に浸りながらre:Playの速報をお送りしたいと思います

会場

f:id:flect-ueda:20191206171311j:plain 各レセプション会場でもすごい人数ですが、一堂に集まるのでとにかく広いです。 セキュリティのためか持ち込むものも厳格に決められています。 とはいえ、飲食物は用意していただけるので特に必要なものはありません。

食事

会場につくとまず食事 f:id:flect-ueda:20191206171257j:plain 様々な料理が屋台として出店されています。

1つ1つの食事の量は少なめなので食べ比べる楽しみがあります。

また食事とは別に、飲み物を提供する場所が点在しており、好きなものを注文できます。 f:id:flect-ueda:20191206172622j:plain

テント

大きなテントがいくつか用意されておりそれぞれ、ライブやハウスミュージックが大音量でかかっています。 f:id:flect-ueda:20191206172825j:plain ライブは近くで聞くと重低音が響き、迫力があります。

f:id:flect-ueda:20191206173726j:plain 別のテントではいい感じのリズムで周りを盛り上げます。

ゲーム

大型のゲームと80〜90年代のレトロゲームが設置してあります。 f:id:flect-ueda:20191206172855j:plain 私は試しにボタン早押しゲームに参加しました。

接戦の結果勝利することができましたが、対戦相手とは互いの検討を祝してレトロゲームをやったりと楽しめました。

こういった意図しない出会いの場が生まれるものre:inventのいいところだと思います。

ドローン

最後はドローンです。夜空に様々な形を繰り出す姿は圧巻です。

最後にIntelのロゴを表示するのはご愛嬌でしたが、、、 f:id:flect-ueda:20191206173222j:plain

大変エキサイティングな体験ができた1週間でした。

名残惜しいですがこれから帰国です。

この体験を業務で生かして行こうと思います。