Rのつく財団入り口

ITエンジニア界隈で本やイベント、技術系の話などを書いています。

【感想】『AWSによるサーバーレスアーキテクチャ』:後編

AWSによるサーバーレスアーキテクチャ

 同書の感想記事後編です。

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

  • 作者:Peter Sbarski
  • 発売日: 2018/03/14
  • メディア: 単行本(ソフトカバー)

第3部 アーキテクチャの拡張

f:id:iwasiman:20210306162532p:plain
AWSによるサーバーレスアーキテクチャ

第8章 ストレージ

 AWS正史では2004年開始のSQSに続いて2006年開始、最古のサービスであり中核的なサービスであり、サーバーレスでも重要なS3を深掘りしていく章。最初のサービスの話は以下のブログ記事が詳しいです。

dev.classmethod.jp

8.1 賢いストレージ
  • バージョニング:切り替えると費用が掛かるかバージョン管理できる。
  • 静的ウェブサイトのホスティング低コストでHTMLやCSSJavaScriptを公開できる。ちなみに本書に出てくるA Cloud Guruクローラー対策でレンダリング済みの静的HTMLバージョンをどうしても提供する必要があったため、S3→Netlifyに移行したとのこと。
  • ストレージクラス:本書で紹介されているのは スタンダード/Standard_IA/Glacier/Reduced Redundancy(定冗長化ストレージ)
  • ライフサイクル管理:1か月後にGlacierに移行、などが設定できる。
  • Tarnsfer Acceleration:あるバケットに世界中からアップロードしたり、大陸間でテラバイト級データを転送する時はバケットの設定でこれを有効にすると少し高速になる。
  • イベント通知:オブジェクト生成などでSNS/SQS/Lambdaに通知できる。ポリシーが必要。

 AWS認定の試験にもよく出るS3の問題、ストレージクラスでGlacierは最初はS3のストレージの中のひとつの扱いでしたが、その後Amazon S3 Glacierとして1つの別のサービスのような扱いになりましたね。

aws.amazon.com

Netlifyも静的サイトホスティングのサービスとしてよく耳にします。

www.netlify.com

8.2 セキュアなアップロード

 サンプルで作ってきた24-Hour Videoのサービスにアップロード機能を付けていきます。

  • ユーザーがファイルアップロード操作すると、API Gatewayでカスタムオーソライザー(Lambdaオーソライザー)が認証
  • その後新規のLambda関数が、シークレットアクセスキーを使ったHMAC署名を作って認証情報とする
  • S3への実際のアップロードが検知されて、これまで作ってきたLambda関数が動いてトランスコード実行

 突然出てくる「HMAC署名」(keyed-Hash Message Authentication Code)ってなんぞ?と思うのですが、次の章でまたこのへんは作り直しになります。

  • アップロードを実行するIAMユーザにポリシーを新規作成
  • 新規のLambda関数。cryptモジュールというものを新規にrequireし、環境変数で外から指定したシークレットアクセスキーを元にHMAC署名を生成、ポリシーをJSオブジェクト形式で作り、アップロード先のURLなどと一緒に返す。内部処理が幾つかあるので、本書で採用しているAsyncモジュールの非同期ウォーターフォール方式で繋げる。
  • API GatewayでこのLambda関数と繋げたAPIを作り、CORSを有効化。
  • アップロードするS3バケットも、CORS設定でPOSTを有効化するよう変更。
  • 画面のJavaScriptjQuery実装ではアップロードボタンを押したらまず上のAPI Gatewayのエンドポイントを叩いてポリシーや署名などを取得。次に指定されたURLに対してファイルの実体をPOSTしてアップロード。
  • アップロードイベントの時間が採れるので、HTML側ではだんだん伸びるプログレスバーも実装。

これは署名付きURLでなく、https://s3.amazonaws.com/{バケット名} に直接POSTするやり方ですね。

8.3 ファイルへのアクセス制限

 今度はアップロードした動画は登録済みのユーザーだけが見られるようにします。

  • 変換後の動画が入っているS3バケットのポリシーを削除。個々のオブジェクトのEveryoneによる読み取り権限も削除。
  • 動画一覧を返すLambda関数の個々の動画URLは、署名付きURLに変更。デフォルト有効期限15分なので30分にする。
const params = {Bucket: bucket_name, key: file_name, Expires: 18000};
let url = s3.getSignedUrl('getObject', params);
  • 一覧画面を作るJavaScriptで、この動画URLをリンク先に持たせるように変更。

 以前読んだ『Amazon Web Servicesを使ったサーバーレスアプリケーション開発ガイド』も画像をアップロードできるサービスを途中の題材にしていて、動画か画像の差はあれ本書の『24-Hour Video』サービスと本質的には似たようなものです。
 あちらは認証はフロントエンドからCognitoを呼ぶ、ファイルアップロードは最初から署名付きURL、画像一覧はDynamoDBに持って……と本書とはまた若干違うアーキテクチャ構成になっており、違う実現方法を見るのも参考になります。

https://iwasiman.hatenablog.com/entry/20210315-serverless-app-dev-guideiwasiman.hatenablog.com

第9章 データベース

 サーバーレスでもやっぱり出てくるデータベース。場合によってはドキュメントデータベースやグラフデータベースが役に立つケースもあり目標と要件次第。NoSQLのアプローチでスケーラブルなキー・バリュー・ストアと高速な検索、扱いやすいJSON形式で……ということで本書が紹介するソリューションは……てっきりDynamoDBが出てくるのかと思っていたら、なぜかGoogle謹製Firebaseが登場します。
 うーん理由がいまいち分からないのですがコストでしょうか、他のクラウドプラットフォームの技術とも簡単に繋げられる例ということでしょうか。謎ですがFirebaseのことも学べておオトクな本となっています。

9.1 Firebase入門
  • Firebaseという名詞自体は、メッセージング、ストレージ、ホスティングなど19の製品コレクションからなるプラットフォーム。Firebase社が開発し、2014年にGoogleが買収。
  • mBaaS: mobile Backend as a Service の代表格として、スマホアプリ開発のバックエンドなどでよく使われる。
  • ややこしいがその中にFirebase Realtime Databaseがあり、本書ではこれをFirebaseと呼んで使用。
  • HTTPのWebSocketでクライアントとデータを同期でき、リアルタイム。スキーマレス。
  • データは全てJSON形式。編集しやすい。一方単純なので構造化やクエリでは柔軟性に欠けている。
  • 「ノード」というのがテーブル相当、キーと対応付けられる。中のデータ構造は平坦な方がよい。
  • データの読み書き権限などを「セキュリティルール」というもので設定。JSの連想配列のような形式でバリデーションルールが設定できる。

firebase.google.com

9.2 24-Hour VideoへのFirebaseの追加

 作ってきた動画サービスにFirebaseを組み込みます。以下のような感じです。

  • S3バケットに新しい動画をアップロードしたらそこで既存のLambda関数起動、ジョブが作られるのは同じ。
  • Elastic Transcoderが動画のトランスコードを開始する前に、Firebaseに飛んでこの動画1件のメタデータをINSERT。
  • トランスコードが終了して閲覧可能になった時もSNS→Lambdaとイベントが渡っていたが、ここでもFirebaseに飛んで動画のメタデータをUPDATE。
  • 動画一覧の画面ではデータはFirebaseから取得するように変わり、今まで作ってきた動画リスト取得のLambda関数は廃止。

Firebaseのセットアップはスクショ入りで詳しく解説されています。

  • アカウントを作り、プロジェクトを追加。
  • データベースを1つ作る。データベースごとにURLが https://{データベース名}.firebaseio.com/ のように一意。これが必要になる。
  • セキュリティルールを作る。
  • Lambda関数からアクセスできるようにFirebaseの画面から「サービスアカウント」を作っていく。種類でLambdaが選べる。JSON秘密鍵が出力される。

 そして動画トランスコード用のLambda関数も変更していきます。

  • npmでfirebaseモジュールを入れる。ZIPに固めるための設定で*.jsonファイルも含めるように変更。
  • Lambdaの環境変数にFirebaseのデータベースURL、サービスアカウント名を設定しておく。
  • Lambda関数のコードは以下のような感じ。
// 初期処理
let firebase = require('firebase');
firebase.initializeApp({
  serviceAccount: {環境変数から取得}
  databaseUrl: {環境変数から取得}
});

exports.hander = function(event, context, callback) {
  // コールバックが呼び出されるとLambdaが待つのをやめて一時停止
  context.callbackWaitsForEmptyEventLoop = false;
  
  // 中の内部関数で...
  let database = firebase.database.ref(); // ツリーのルート取得
  // videosがノード(=RDBのテ―ブル)、そこにkeyをキーとして1件INSERT
  database.child('videos').child(key)
    .set({
      transcoding: true
    })
    .then(function (){
      callback(null, 'Firebaseに保存完了!');
    })
    .catch(function (err) {
      callback(err); // エラー処理
    });
  
};

context.callbackWaitsForEmptyEventLoop = false; という謎の処理が出てきますが、firebaseモジュールでなくサーバー用のSDKであるfirebase-adminモジュールをnpmでインストールした場合は不要とのことです。こういう日本語版の注釈があるのはありがたいです。

また、動画のトランスコードが終わった後に実行される別のLambda関数も作っていきます。Firebaseへのアクセス部分が以下のような感じ。

  // いろいろ省略
  let database = firebase.database.ref(); // ツリーのルート取得
  // videosがノード(=RDBのテ―ブル)、videoKeyをキーとして1件UPDATE
  database.child('videos').child(videoKey)
    .set({
      transcoding: false,
      key: {S3のキー(ファイル名)},
      bucket: {バケット名}
    })
    .catch(function (err) {
      callback(err); // エラー処理
    });

database.child().child() と2回続くのが不思議な感じですがこちらが更新のコード。前からあった属性transcodingはtrueからfalseに変わり、新しい属性keybucketが加わっています。スキーマレスだから途中から属性が追加されても大丈夫……というこのあたりはDynamoDBと同じですね。

 また画面の方も更新していきます。

  • Firebaseのコンソール画面でログインした後にプロジェクト単位で「ウェブアプリにFirebaseを追加」ボタンを押すと、apiKey, authDomein, databaseURL ... などが表示されるのでこれをコピーして入手。
  • 今回の24 Hour Videoでは定数ファイルにapiKeydatabaseURLを保存。firebase提供のfirebase.jsもソースに含める。
  • JavaScriptのFirebase接続部分は以下のような感じ。
firebase.initializeApp({設定群});
// 接続しているかが分かるFirebase提供のフラグ。
let is ConnectRef = firebase.database().ref('.info/connected');
let nodeRef = firebase.database.ref('videos'); // videosノードを参照

// このフラグで画面が今オンライン/オフラインかが分かって制御できる
isConnectRef.on('value', function(snap) {
  if (snap.val() ===true) {
    // UIでローディング表示を消したり
  }
});

// 新しい動画が追加されると実行されるクロージャ
nodeRef.on('child_added', function(childSnapshot) {
  // UIで動画1件の表示を足す処理。変数childSnapshotに中身が入っている
});
// 既存の動画が変更されると実行されるクロージャ
nodeRef.on('child_changed', function(childSnapshot) {
  // UIで動画1件の表示を変更する処理。変数childSnapshotに中身が入っている
});

分かったような分からないようなままでいたJSのクロージャが出てきた……!
 通信にWebSocketを使っているのでポーリング不要、Firebase側に変更が起こると自動的にプッシュされて上のnodeRef.on...のところにくるので、そこにUI制御を入れれば自動で画面が変わるとのこと。うーんWebSocketも奥が深そうです。

9.3 ファイルへのアクセスの保護

 今度はFirebaseから動画一覧を取得した後、Lambda関数を呼んで署名付きURLも取得して画面の描画に使うように拡張していきます。
 普通に動画1件に対して1回実行されるLambda関数なら以下。

s3.getSignedUrl('getObject', {バケットとキーとExpres});

しかしこれはパフォーマンスが悪いので動画全件でLambda関数1回でやろう……ということで、本書では6章に出てくる非同期処理のAsnycモジュールでforEachOf関数でぐるぐる回し、レスポンスのボディの中で配列で持って返すコードを紹介しています。まあここは今なら別のやり方になるでしょう。

 そしてよりセキュリティを向上させ、ログインしなければ動画が見られないようにするため、Auth0 を用いて認証しFirebaseのリソースと委任トークンを使ってやりとりする実装方式を示しています。
 ここは日本語版の注釈があり2018/2月より非推奨。OpenId Connect 準拠のフローで行うのが推奨とのことです。日本語でアクセス可能な情報が出たら翔泳社サイトから提供予定とのことでしたが、その後の追加情報はないようですね。

www.shoeisha.co.jp

 データベースにこのFirebaseを使う方法は個人でサービス開発をしていたりする方の話やFlutter, React Nativeなどのスマホアプリ開発、技術同人誌の題材としてもよく見かけます。
 読んでいくと始め方はそれほど難しくないし、大まかにはDynamoDBと同じような感じで使えそうなのが分かりました。このへんの手軽さが個人開発などで受けているのでしょう。

 なお本書ではFirebase Realtime Databaseを採用していますが、2018年時点ではベータ版だった後発の次世代版のNoSQLデータベース、Firebase Cloud Firestore が現在では推奨となっています。名前にfireが2回出てきてややこしい...

firebase.google.com

第10章 仕上げの学習

 最後はまとめと他のトピックの章。

10.1 デプロイとフレームワーク
  • 規模が大きくなってくると構造化、整然としたデプロイのためにフレームワークを使った方が良い。
  • Serverless Framework: Serverless,Incというスタートアップ企業製。誰でも機能を追加できるプラグインがシステムがあり、拡大中。
  • AWS SAM: サーバーレスより前からあるAWSリソース自動生成サービス CloudFormation を拡張したもの。こちらはAWS製。

www.serverless.com aws.amazon.com

10.2 よりよいマイクロサービスのために

 本書では各サービスがデータ保存のメカニズムがサービスごとに完全に独立している(=DBを共用しない)ことがマイクロサービスの条件だ、という文脈で話しています。『マイクロサービスパターン』では「サービス毎にそれぞれ別に開発してデプロイできること」と定義しているのでニアリーイコールですね。

  • 最終的には全てのデータの整合が採れた状態になる結果整合性の理解が大事。
  • 例えばAPI Gateway→あるサービスが処理→そのサービスが順々に他のサービス群を呼ぶアーキテクチャ。これだと互いに密結合になってしまい、デプロイ速度が鈍る。
  • それよりはAPI Gateway→あるサービスが処理→Kinesis Data Streamsにレコードプッシュ→このKinesisをサブスクライブしている各サービス群がそれぞれ起動するアーキテクチャ。こちらは疎結合で個々のサービスの開発も楽になる。ただしエラー処理と修復が難しい。
  • であればエラー処理用マイクロサービスを別に作り、順に他のサービス群を呼んでロールバックさせていくとよい。
  • Lambdaは再試行回数を経過してもイベント処理に失敗するとデッドレターキューにメッセージを送る仕組みを持っている。SNSSQSを選べる。ここからエラー処理用マイクロサービスを呼ぶようにするとよい。

dev.classmethod.jp

10.3 AWS Step Functions

 Lambda関数、EC3インスタンス上のコード、インフラ上のコード、それぞれからワークフローを作成して制御してくれるStep Functionsの話。

aws.amazon.com

例としてS3バケットに画像が上がったらそれぞれ別々の時間がかかる変換処理を行う3つのLambda関数が動く仕組みを上手く作りたい……というユースケースを挙げています。

  • S3バケットに登録のイベントをトリガーににしてStep Functionsを実行するLambda関数作成。
let AWS = require('aws-sdk');
let stepFunctions = new AWS.StepFunctions();
let params = {
  stateMachineArn: {ステートマシンのARN},
  input: {この例では入力となるバケット名とファイル名},
  name: {好きなラベル名}
};
stepFunctions.startExecution(params, function(err, data) {
  if (err) {
    callback(err);
  } else {
    callback(null, 'ステートマシン実行開始! execututionArn: ' + data.executionArn);
  }
});
  • Step Functionsを始めて「ステートマシン」を作成していく。GUIで3つの関数を配置すると、Amazon States Languageという連想配列っぽい書式で描かれる。処理が終わったら起動される通知送信関数というものが別に作られ、ARNが割り当てられる。
  • 定期的にステートマシンの実行状況を確認する関数を以下のように別に作ればよい。返ってきたら他のサービスに繋げるなど。
let pamrs = { executionArn : {実行開始時に返ってくるArn} };
stepFunctions.describeExecution(params, function(err, data) {
  if (err) {
  } else {
    //この中にステートマシン内部で実行された関数群がcallbackに渡した値が入っている
    console.log(data); 
  }
});

Step Functionsの活用事例というのはLambda本体に比べるとあまり見ない気がしますが、実は色々な用途に使えそうです。

10.4 AWS Marketplaceが開くビジネスチャンス

 2016年に発表、API Gatewayで作ったAPISaaS製品としてマーケットで売ることができるAWS Marketplaceの話。

10.5 これからの展開のために

 最後は作ってきた24-Hour Video サービスの応用の演習問題を上げて、この先も学んでいきましょう、学習のための最良の方法は実践です、これから急速に発展していくサーバーレスの旅は始まったばかりです……と最後を締めています。

付録A~G

約400ページの本なのに付録が80ページぐらいあって圧倒的です。AWSサービスの紹介、本書のサンプルの24-Hour Videoサービスを実際に作っていくためのセットアップ手順、認証/認可の話の深掘り、LambaでLinuxコマンドを実行して見た時の内部の話やLambdaプロキシ統合を使わない場合でリクエストの中身をAPI Gatewayでどう取り出していくか……など、現場で役に立ちそうな実践的な付録がついています。
 最後のServerless Framework/SAMの話はServerlessの作者自らによる執筆ということで、かなり詳しく書かれています。

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

  • 作者:Peter Sbarski
  • 発売日: 2018/03/14
  • メディア: 単行本(ソフトカバー)

まとめ:サーバーレスのアーキテクチャ&実際のサービス開発まで両方学べる本

サンプルアプリケーション:
 24-Hour Videoという動画共有サイトを作っていきますがかなり本格的です。日本で読める商業本でのLambda関数の記述は、(技術同人誌発の本を除くと)たいていPythonなので、JavaScript(Node.js)のサンプルが読めるという点でも貴重でしょう。変数宣言がvarだったり非同期処理にAsyncというモジュールを使っていたり若干の古さがあるのでそこは注意です。
 サイトのフロントエンド側もJavaScript実装はなんと必殺のjQueryなので、ここも適宜読み替えるなり概要を知る形かと思います。AWSにフォーカスした本なのにデータベースでなぜDynamoDBでなくFirebaseを選んでいるのか最後までよく分からなかったのですが(笑)、他のクラウドプラットフォームとも繋げられるという例なのでしょうか。まあこのへんも下のインタビュー記事にある通り、技術動向に沿って組み替えて進化させていくぐらいのやり方がサーバーレス流なのでしょう。
 また、各章の章末に正解のない応用の演習問題が並んでいるあたりが、さすがに学ぶ人のための資料として手馴れている感じです。

acloudguru.com

本の感想:
 サーバーレスのアーキテクチャや設計パターンがメインの本かと思いきや、そのあたりの解説も深掘りしているのですがAWSの各種サービスの使い方、本格的なチュートリアルまで入ってサーバーレス全部載せのおトクな本でした。
 A Cloud Guru という学習サイトを運営している方だけあって説明も親切で分かりやすく、日本語版の文章もストレスなく読みやすいです。やはりその筋の第一人者の書籍は違うな……と思いました。2018年の本ですが、サーバーレスに触れるなら一度目を通しておいて損はないと思います。

原著はこんな表紙なんですね。どう見ても日本語版の方がスタイリッシュである……w www.manning.com

著者の方のGitHubに作中のコードも格納されています。 github.com

翔泳社のサポートページ。 www.shoeisha.co.jp

監修者の吉田真吾さんへのインタビュー。使っている技術が古くなったらどう入れ替えるか考えながら読む位で良い、という話をされています。 codezine.jp

おなじみクラスメソッドさんの書評。 dev.classmethod.jp

こちらのブログではGoodポイントのほかにBadポイントも両方上げられています。 ponzmild.hatenablog.com

海外レビューの翻訳。直訳だけどここでも高評価。 www.injpok.tokyo

Qiitaの内容まとめ記事。 qiita.com

関連書籍

以前上げた『基礎から学ぶサーバーレス開発』の感想記事にまとめています。

iwasiman.hatenablog.com

自分用メモ:
画像ソフトを弄っていて気付いたのですがserverless frameworkのロゴはMacだと最初から入っている Ayuthaya っぽいです。
serverlessconf 関係のページでよくお見かけするワイルドな斜体のフォントは何を使ってるのでしょうね。教えてエライ人!