この広告は、90日以上更新していないブログに表示しています。
先週は 以下の記事が何故かはてブのホットエントリ入り、多数のアクセスありがとうございました。著者の方からも反応いただいてありがたい限りです。
【感想】『Amazon Web Servicesを使ったサーバーレスアプリケーション開発ガイド』:Lambdaで本格サービス開発まで - Rのつく財団入り口
自分的に勝手にサーバーレス強化月間をやっていたので読んだ本だったのですが、その続きの3冊目、締めで読んだ本がこの『AWSによるサーバーレスアーキテクチャ』。著者は技術カンファレンス「Serverlessconf」の責任者でもあるPeter Sbarskiさん、サーバーレスの第一人者による書籍です。

まずはサーバーレスの考え方そのものから。
PaaSが登場。しかしPaaS側に合わせるために余分な開発がいたりする。よく関連して語られるSOAやマイクロサービスについても、本書では関連を述べています。
(SOA: Service Oriented Architecture)それぞれイコールではないけど部分的に重なり合っているわけですね。
また従来の3層アプリケーションでは階層を増やすとどんどん複雑になっていくのを例に取り、サーバーレスのアプローチを正しく使えば複雑さを軽減、変更容易性を保てる…という話で階層の用語の混乱の話が出てきます。
(tier): 大きなコンポーネントを分離するモジュール境界。プレゼンテーションティア、アプリケーションティア、データティアの3層が基本。物理的には別インフラの場合もある。(DBサーバーを分けたり)(layer): アプリケーションの中の特定の機能を実行する論理的な断片。1つのティアの中に複数の階層。プレゼンテーション層の中にUIコンポーネント、プレゼンテーションロジック…。アプリケーション層の中に、ドメイン、ビジネスエンティティ、データアクセス層…などなど。これは自分もごっちゃにして使っていたかも...! でも設計を扱った書籍やWeb情報などでも同じに扱っていることがあるような...?
原則は5つと述べています。
コンピューティングサービスを使ったオンデマンドのコード実行:カスタムコードは粒度の細かい関数として記述、単独で独立して実行。
目的が1つでステートレスな関数:単一責任原則(SRP: Single Responsibility Principle) に従って設計する。一つの仕事をする関数はテストしやすく副作用も少ない。またステートレスなので、一時ファイルなどのリソースやプロセスを再利用してはいけない。ステートレス→スケール可能。とても強力。
プッシュベースのイベント駆動パイプライン:イベント駆動で、ある関数が仕事をしたら通知を受けて次の関数やサービスが仕事を…と繋げていく。コストが下がり複雑度が軽減。ただし必ずしも適切出ない場合もあり、Lambdaがポーリングしたりする場合もある。
より厚く強力なフロントエンド:Lambdaは使った時間だけ課金。すべてを一旦バックエンドに飛ばすのでなく、フロントエンドから各種サービスと直接API通信してよい。しかし秘密情報の処理や整合性が重要な処理ではLambdaを使ったほうがよい場合もある。
サ ードパーティサービスの活用:使えるサービスやAPIがあったらどんどん利用して「巨人の肩の上に立つ」。
少しづつ移行できるのはサーバーレスの長所。プロトタイプを作って試すと良い。妥協を強いられる場合もあり、ゆっくりと慎重に進むべき…と述べています。
短所が以下。
長所が以下。
作者のピーター・スバースキさんはServerlessconfのオーガナイザーでもあり企業のVice President、コンピューターサイエンスの博士号取得。同じく大学の博士が序文、翻訳の方も日本のServerlessコミュニティの方...と錚々たる顔ぶれだけあって、文章は非常に読みやすく学びになります。
serverlessconf.iotokyo.serverlessconf.io
どのような場面でどんな設計をしていくのがよいかの章。
以下のような場面で役に立つとしています。
AWS IoT Coreもあり。Kinesis Data Streams+Lambdaが強力。Amazon Echoなど。 アーキテクチャの大きな分け方としてはバックエンドとグルー(glue, のり, ワークフロー実行のパイプライン)があるとし、組み合わせて使っていくと述べています。
本書の作者さんらによるサービスのA Cloud Guru のグールー(Guru)は「導師」のような尊称なのでちょい注意ですね。
バックエンド:
一般的なアプローチ。原則の「分厚いフロントエンド」と「サードーパーティの活用」が重要になる。フロントから直接APIを通して認証や検索のサービス、データベースとやりとりしてもよい。フロントでやってはいけない仕事はバックエンドのLambdaにする。Lambda関数の粒度を適度に分けるのも大事。
レガシーAPIプロキシ:
APIGatewayの中でリクエストを変換して他のサービスを呼んだりできる。複雑な変換はできないので一度Lambdaを介するケースもある。Node.jsには入り口が古いSOAP形式のサービスとも変換できるライブラリがあったりする。
ハイブリッドシステム:
二者択一ではないので、レガシーなシステムの一部にサーバーレステクノロジーを導入することもできる。一部の機能をAPIGatewayを介してLambdaで行うなど。ゆっくりとサーバーレスに移行することも可能。
GraphQL:
Facebook製、RESTに変わるものとして設計されたデータクエリ言語。ひとつのLambda関数がGraphQLを担当して複数のデータソースにクエリを送ることができる。2017−2018より登場したモバイル用のAWS AppSyncも検索にGraphQLを使っている。
グルー:
「Compute-as-glueアーキテクチャ」と呼ぶ。サービスとサービスの間の糊(グルー)をLamda関数が受け持ってイベント駆動型のパイプラインが作れる。S3のファイル操作、SNSの通知、Lambda自力のポーリング…など各イベントごとに小さなLambdaがそれぞれの仕事をして、組み合わさって複雑なタスクも比較的簡単に実現できる。
リアルタイム処理:
ログ、イベント、トランザクション、ソーシャルメディアからの入力など膨大なデータをKinesis Data Streamsga処理できる。APIGatewayの後ろにおけるし、クライアント側やLambda関数からもデータ追加可能。
本書では「ソフトウェア設計の問題に対するアーキテクチャ上の解決策」だとして、サーバーレス以前からあるもの含めていくつか紹介しています。
Command パターン:
リクエストの違いに基づいてクライアントをパラメータ化したり、キューイング、ロギング、取り消し操作をサポートするためにリクエストをオブジェクトにカプセル化すること。GoFデザインパターンにもある。
サーバーレス的にはAPIGatewayを介して1つのLambdaにリクエストが飛んでくるとその中のオブジェクトに命令が入っており、命令に応じてそれぞれ別のLambdaを呼び出したり…と分岐できる。RestfulURIをいちいち作らなくてもよくなる。ただし検索などで使うと複数のLambdaが入る分、レイテンシーが増す恐れもある。
Messaging パターン:
関数やサービスの直接の相互依存関係をなくし、イベントやレコード、リクエストをキューに格納するパターン。分散システムやマイクロサービスでおなじみ。密結合が減って将来の変更も楽になる。
複数のデータソースからやりたいことがSQSかKinesis Data Streamsのキューにどんどん貯まり、ディスパッチ用のLambda関数が定期実行でキューを見にい行き、それぞれの命令によって別のLambda関数を呼び出して処理。SQSはSNSトピックにもサブスクライブできるので、SQSにキュー追加→SNSトピックに通知→見ているすべてのLambda関数が一斉起動…という展開もできる。
ここでLambda関数がまた別のキューに登録→別のディスパッチ用のLambdaが定期的に見に行って…という設計もできる。
Priority queue パターン:
処理の優先度をAWS任せでなく自分で決めたいとき。すぐ処理したいものはコストが高いサービスに渡して優先順位1のSNS/SQSへ、そうでないものは優先順位2の別のSNS/SQSへ…などと分けていく。有料ユーザーと無料ユーザーで処理方法を分けたりするときに使える。複雑度が増すので控えめにしたほうがよい。
Fanout パターン:
S3にオブジェクト登録のイベント発生→Lambda起動 だとひとつしか起動しない。CommandパターンでこのLambda関数が別のLambda群を呼んでいくこともできるがコードが必要になる。
こんな時はイベント発生→SNSのトピックにメッセージ追加→サブスクライブしているLambda関数群が一斉起動、と処理できる。扇形に広がっていくので「ファンアウト」と呼ぶ。並列に実行していくイベント駆動アーキテクチャで効果的。
Pipes and filters パターン:
データの変換を目的とするコンポーネントがフィルタ、フィルタとフィルタの間で受け渡すのがパイプ。粒度の細かいLambda関数をフィルタとして順番に処理していくことで、複雑なタスクを分解してそれぞれを別のサービスとして整理して扱うことができる。全体がひとつの関数のようなイメージ。
Lambda関数は単一責任原則、冪等に注意して作っていく。このパターンがアーキテクチャとして大きくなったのがCompute-as-glueアーキテクチャ。
図も多く、実際のサービスの事例もあり分かりやすく読むことができました。
今度は具体的に、YouTubeライクな動画投稿サービス「24-Hour Video」の構築を通してサーバーレスを体感していく章。
Amazon Elastic Transcoderというサービスを呼び出して動画を変換、別のS3バケットへという流れです。Elastic Transcoderは若干料金がかかるが他のは無料枠に収まるということでうまく考えてあります。
Lambda関数は本書ではJSで、第1の関数が以下のような感じ。
exports.handler =function(event, context, callback){// いろいろ省略let params ={ PipelineId:{使用開始したElastic TranscoderのID}, OutputKeyPrefix:{出力フォルダ}, Input:{ Key:{入力ファイル名}}, Outputs:[{出力のプリセット群}]}; elasticTranscoder.createJob(params,function(error, data){if (error){ callback(error);});};
2018年の本なので原文のコードだと変数宣言がvarなのが若干気になりますが、コールバック関数がある以外はPythonとそれほど変わらずでした。
またJavaScriptではおなじみのnpmにはrun-local-lambda というモジュールがあり、package.json の中に実行用のスクリプトを書いたり入力引数のeventの中になる値をJSONで書いておいて実行させたり、AWSへの関数デプロイもコマンドで出来たりするそうです。これは便利そうです。
S3側でのObjectCreatedのイベントソースでのこの関数を設定していくのは、画面からいつもどおり。
Elastic TranscoderのIDを取得するところで入力のS3バケットが一意になってLambdaコード内では不要なのは分かりましたが、出力先のS3バケットがどこで決まるのかがわかりませんでした。これもElastic Transcoderで自動的に決まるのかな?
ObjectCreateのイベントが起こったら上のSNSトピックに飛ぶよう設定。アクセス権限を変える第2のLambda関数が以下のような感じ。
// いろいろ省略let params ={ Bucket:{バケット名}, Key:{キーのファイル名}, ACL:'public-read'}; s3.putObjectAcl(params,function(err, data){if (err){ callback(err);}});
アクセスコントロールリストの変更をコードから行うのも、分かると難しくないです。
そして3.2で作った通知用のSNSトピックの設定でサブスクリプションを作成し、届ける先をこの第2のLambda関数にします。
今度は第3のLambda関数で、動画からメタデータを取得するのにffprobeというコマンドラインユーティリティを使います。
// exports.handlerの内部で呼ぶ内部関数で、// S3バケット内の動画をローカルの/tmpに展開let file = fs.createWriteStream((/tmp' + {ローカル上のファイル名} );let stream= s3.getObject({ Bucket:{バケット名}, Key:{キーのファイル名}}).createReadStream().pipe(file);steam.on('error',function(error){ callback(error);});stream.on('close',function(){// 展開し終わったら次の内部関数へ});// ffprobe ユーティリティを使ってメタデータを抽出let cmd ='bin/ffprobe -.....{オプションやファイル名など}';// exec関数でコマンド実行!exec(cmd,function(error, stdout, stderr){if (error ===null){// エラーがなかったら変数stdoutの中が出力のメタデータなので// メタデータ用の別のS3バケットに保存}else{ console.log(stderr); callback(error);}});
使うffprobeユーティリティは予め入手、bin/ディレクトリに置いてLambda関数と一緒にZIPにして登録するというやり方です。
/tmpに動画を展開しているので、512MBより大きいと失敗することになります。(よく試験に出るやつ...)
あとは3.2で作ったSNSのサブスクリプションをもうひとつ作って、送信先をこの第3のLambda関数にします。
全部AWSのサービスでやらなければいけない訳ではなくて、こんな風にふつうのユーティリティを同梱して利用するやり方もあるのですね。
こうして動画投稿アプリケーションが一式できてしまいました。個々のLambdaコードも短いし、繋がりを理解するとこんなにすぐできちゃうのか…と思います。本の方ではテスト方法もサポートしてあります。
そして本書で面白いのが章末に「演習問題」があること。読んでいて「このコードは仕事レベルだったら入力チェックもっと厳しくやるよな…」とか「サンプルだから動画の削除機能はないのかな?」とか思うのですが、そうしたところや機能の拡張の話が、正解のない演習問題として記載されています。
学校の勉強だったら時間が余った人はチャレンジしてみましょう…的な位置づけです。読者に自分で考えさせながら学ばせるこのやり方はよいですね。
AWSに的を絞ってセキュリティ、ログ、アラートなどをおさらいしていく章。
ここはお馴染みIAMの話。
MFA(多要素認証)でセキュリティを強くしておくのがオススメ。CloudCheckrが使いやすい。AWS Trusted Adviserは無料だと機能制限がある。Cost Explorerを有効化するとしばらく時間が掛かった後、向こう3ヶ月の料金が予測できる。Simple Monthly Calculatorも使える。本書は2018年なので100ms単位と書いてありますが、Lambda関数の実行時間の課金は2020年12月頃より、より細かく1ms単位に更新されていますね。
この章はAWSを使っていたり学んでいたりする人ならだいたい知っている話でした。
Amazon Cognito とAuth0を使いながらサーバーレスの認証周りを述べていく章。
Cognito やAuth0で認証を楽に実装できるる。JWT(JSON Web Token)を使用。Amazon Cognito
Cognitoを通して委任トークンを使って書き込み。CognitoはSTS(Security Token Service)と通信してAWS認証情報が認められたら、そのAWS認証情報をがクライアントに返す。Auth0を使う。Auth0
(MFA)やTouch IDもサポートした万能IDプラットフォーム。3章で作ったYouTubeライクな動画投稿サービス「24-Hour Video」に認証機能を追加していきます。以下のような処理の流れです。
Auth0に追加する。Auth0と繋がって認証、成功するとJWTとユーザープロフィールを返す。これはJavaScriptからlocalStorageに保存。JWTを一緒に送る。通過すると本来のLambda関数に来て、飛んできたJWTを使ってAuth0と通信、ユーザ情報を返す。ここの中身は先の章で実装。
実はクライアントにAWS SDKをダウンロードすれば、Lambda関数を直接呼ぶことも一応できる。しかし密結合になってしまい様々な付加機能が使えないので、RESTfulなURLからAPIGatewayを経由するのがよい。
jQuery、デザインはBootstrapを使用。Auth0で新しいアカウント登録。設定でモバイル/ネイティブアプリ、SPA、Webアプリを選べる。無料使用ではソーシャルIDプロバイダーから選べるのは2つまで。index.html の画面を修正していきます。
Auth0側のjsファイルをCDNから読み込み追加。config.js追加、中に設定としてAuth0の「ドメイン」と「クライアントID」を固定値として保存。user-controller.jsがそのまま使えるよう本書で提供。userControllerというJSオブジェクトが一式を処理。画面のログインボタンを押すとAuth0側の処理を呼んで認証、成功するとlocalStorageにJWTを保存。以降認証にこの値を使う。フロントエンドメインの画面を動かすだけなら上の5.2で完結ですが、次にクライアント→APIGateway→Lambdaと繋げていきます。下がLambdaオーソライザーを通過した後の、ユーザ情報問合せ用のuser-profileのLambda関数のイメージ。
// jsonwebtokenとrequestモジュールをインポートしておくexprots.handler =function(event, context, callback){if (!event.authToken){// トークンがない場合の処理}// Authorization: Bearer XXXと飛んでくるのでXXXを抽出let token =event.authToken.split(' ')[1];let secretBuffer =new Buffer({環境変数からAuth0シークレット取得}); jwt.verify(token, secretBuffer,function(err, decoded){if (err){// エラー処理でcallback('失敗!!'); を呼ぶ}else{let body ={'id_token': token};}// Auth0のドメインにある/tokeninfo というリソースを指定。let options ={いろいろ}; request(options,function(error, response, body){if (!error && response.statusCode === 200){ callback(null, body);// 本来の通信先と通信して正常時}else{ callback('失敗!');}});}})};
画面側の実装ではAuth0の「ドメイン」と「クライアントID」という固定値を使いますが、Lambda関数では「ドメイン」と「Auth0シークレット」の2つの固定値なので注意ですね。
jwtモジュール側の関数の呼び方は一緒なので慣れればだいたい同じでしょうか。単純に慣れの話ですが、JavaScriptの方がカッコが多くてcallback関数があるので、ぱっと見はPythonで書いた方が分かりやすいなあと感じました。
user-profile追加。user-profile関数にして、CORSを有効に。event.authToken として取れる。「Lambdaプロキシ統合」を使っていればこれはいらない。{ "authToken": "$input.params('Authorization')"}
そして上のuser-profile関数が動く手前に配置する「カスタムオーソライザー」のやり方。
名前を custom-authorizerとしたLambda関数が以下のような感じです。
export.handler =function(event, context, callback){if (!event.authorizationToken){ callback('トークンがない!');return;}// Authorization: BearerXXX のXXXを取得let token =event.authorizationToken.split(' ')[1];let secretBuffer =new Buffer({環境変数のAuth0シークレット}); jwt.verify(token, secretBuffer,function(err, decoded){if (err){ callback('JWTで認証失敗!');}else{// 下のIAMポリシーがAPI Gatewayに返される。内部関数で生成したり。 callback(null,{JSオブジェクト形式で作られたIAMポリシー});}});};
入力のトークンを使ってこの関数内で自由に認証をカスタマイズできるが、この例だと認証自体はjsonwebtokenモジュールに任せているので呼ぶだけ、ということですね。戻り値でIAMポリシーを作るところはJavaScriptならJavaScriptオブジェクト、Pythonなら辞書型でまあ似たようなものです。
その後の流れがこちら。
「method.request.header.Authorization」を指定。これで上の関数で引数event.authorizationToken から取れるようになる。この「カスタムオーソライザー」は2016年提供開始、その後2018年?ごろからAPIGatewayの中の機能「Lambdaオーソライザー」として名前が変わっています。柔軟にいろいろ認証を工夫できそうです。
dev.classmethod.jpdev.classmethod.jpqiita.com
以前読んだ『Amazon Web Servicesを使ったサーバーレスアプリケーション開発ガイド』では認証に全面的にCognitoを使った実装例が詳しく載っていたので、本書でAuth0とちょうど両方分かって良かったです。カスタマイズの余地もありつつ、なるべく独自実装部分は少なくして認証の仕組み側に任せるという事ですね。
サーバーレスアーキテクチャの心臓だと表しながら、Lambdaを深堀りしていく章。
本書ではサーバーレスとLambdaは同義語ではなく、強力なパターンとアーキテクチャを使った新しいアプローチがサーバーレスアーキテクチャであり、その一部がLambda。FaaS(Function as a Service)もサーバーレスの一部だとしています。
Lambdaが呼べるイベントモデルは以下。
イベント駆動型は「イベントドリブン」と表現することも多いでしょうか、その中に2種類。
S3のファイル登録など。Lambdaにイベントをパブリッシュし、関数を直接実行。Kinesisストリームを定期的に自力でポーリングし、新しいレコードがあったら関数で処理。諸元が以下。
Kinesisのようなストリームベースではアクティブなシャードの数。コールドスタートをなるべく避けてパフォーマンスを上げる方法は以下を上げています。
開発者はコードしか触れませんから、Javaコードで書くとコンテナ初期化時に内部で一式ビルドされてそれからやっと動くのでタイムラグあり。ウォーム状態だとコンパイル済みの状態で再利用されるので軽快に動く……という理屈でしょうか。 JavaScriptやPythonより初期化に掛かりそうなのは分かりますがますが、それだと同じくコンパイルが必要なC#はどうなのかな?と思いました。
なおLambda本体と一緒にJavaScriptならモジュールを一緒にアップロードする方法は、基本はZipで展開後に250MBが上限。その後「Lambdaレイヤー」の機能が追加されて複数の関数で共有が可能に。そして2020年12月から、最大10GBのコンテナイメージの形でもデプロイできるようになりました。
aws.amazon.comaws.amazon.comdev.classmethod.jp
exports.handler = function(event, context, callback) {}callback(Error error, Object result)形式。callback(null, '成功したよ'); が正常終了時。callback('失敗したよ'); が異常終了時。callback(); はcallback(null)と同じで正常終了。console.log("ロギングしよう") とするとCloudWatchに渡る。.error(), .info(), .warn() も実質差なし。ログ用のしっかりしたライブラリを使うのを推奨。API Gatewayから呼ぶ際にARNの最後に:3 のように指定できる。最新版は$LATEST。dev, staging, production とつけるなど。イベントソースからはバージョンでなくエイリアスを指定すると便利。process.env.ENV_VARIABLE_SAMPLE のようにして指定。画面のスクショはすべてちゃんと日本語版になっていてありがたいです。特に古くなってるようなところは見当たりませんでした。
aws lambda create-function {引数でZIPファイルの場所などなど}
のようにコマンドからも操作できる。
JavaScriptでLambda関数を書く際は非同期のコールバック関数が多くなってコールバック地獄に陥りやすいので、シーケンシャルに記述できる「非同期ウォーターフォール」を推奨しています。Asyncモジュールを使ってasync.waterfall([関数, 関数, 関数...], 最後のコールバック関数);
と書くと見た目もわかりやすくなるというもの。
名前が似ていてややこしいですが、このAsyncモジュールは古い時代の話ですね。非同期処理にはその後ES6(ES2015)でPromiseが登場し、ES8(ES2017)でasync/awaitが登場しています。Lambdaランタイムで古いNode.jsを使うのでなければおそらく出番はないでしょう。
例題に登場する24−Hour Videoアプリケーション用に、S3にアクセスして動画一覧を取得するLambda関数、SESを使ってメール送信するLambda関数を中で機能ごとに内部関数に分割し、このAsyncモジュールを使ってシーケンシャルにやっていく例のコードが載っています。
Mocha、TDDのアサーションライブラリのchai、ユニットテスト用のrewireを使ってローカル上でテスト。lambda-test-harnessを選んで作っていくと、他の関数をテストして結果をDynamoDBに格納できる関数が作れる。本書では簡単に触れられているだけですが、Lambda関数の開発時にローカル/AWS上でどう上手くテストしていくかのテーマも、いろいろ奥が深そうです。
バックエンドの様々なAWSサービスとフロントエンドのクライアントアプリを結び付けるインターフェスがAPIGatewayだとして、深掘りしていきます。サービスとの統合が4種類。
モック統合:ダミーのJSONを返したり。
負荷を下げるキャッシング、アルゴリズムを使ってAPI呼び出しの回数を押さえるスロットリングの機能。ロギングはCloudWatchが対応。
スクショを元に実際のAPIを作っていく様子が解説されています。
eventに渡るので楽、通常こちら。一方、リクエストが巨大で一部しか使わない場合は手動の方が良いケースもある。後半では 24-Hour Videoサービスの例として、動画リストを取得するLambda関数、そして画面側のHTMLとJavaScriptの実装例が載っています。
jQueryベースなので、出来上がったAPIGatewayと通信して得られたデータを特定div要素の中に、動画1件用のdiv要素をどんどん複製して表示していく…という当たり前だけど直球の実装でした。
Lambda関数でHTTPリクエストを発行しまくる処理を書いても行けるんですね。AWSの中からAWSの別の場所を攻撃するので、自分自身を標的にしているようで面白いです。
dev/stage/prodなど。ここでステージ変数というものが定義でき、ステージごとに別の値を採れる。APIの各リソースと紐づいたLamda関数を指定する時に変数が使えるので、ステージごとに動かす関数を別に定義したりできる。ちょうど本書を読んでいた頃にAPIGatewayの実物を触って最初はハマったり試したりしていたので、この章は割とすんなり読めました。スクショも全て日本語化されています。
id:iwasiman引退した元TRPGゲーマー。COBOLでもなくPL/Iの金融系レガシー紙駆動開発から脱出→国産メーカー系総合ITベンダーのITエンジニア。所謂SIerのSEだけど仕事はほぼソフトウェアエンジニア/ソフトウェアアーキテクトとして、Web開発でコードを書いたり技術を追ったり時々イベントに行ったり、楽しいエンジニアリングを目指しています。Views are my own.
お気軽にどうぞ~
リンク集:https://lit.link/iwasiman
AIイラスト関連の活動はこちら↓
https://www.chichi-pui.com/users/iwasiman/
https://pixiv.me/iwasiman
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。