書籍『サーバーレスシングルページアプリケーション』を読んで、試してみました。
サーバーレスシングルページアプリケーション ―S3、AWS Lambda、API Gateway、DynamoDB、Cognitoで構築するスケーラブルなWebサービス
- 作者:Ben Rady
- 発売日: 2017/06/23
- メディア: 単行本(ソフトカバー)
いやー、この本いいですねー。実践的でかつ分かりやすい。最近は、FaaS (Function as a Service) っていうんですか?流行りのようですので、興味ある人はぜひ一度試してみるといいと思います。
さて、本の書評はネットを検索するといろいろと出てきます。
yoshiyoshifujii.hatenablog.com
他にもいくつか目にしたのですが、忘れました(すいません)。
ということで、私などがあれこれいうのもおこがましいので、ここでは、実際にチュートリアルを試した際に気になったことやつまづいたことなどを、雑多にメモにしておきたいと思います。 似たようなところで、はまってしまう人の参考になればと思います。
ちなみに、はまったところを書いておられる方もいらっしゃいました。
こちらもご参考にしてください。
さて、あれこれ書き始める前に、今回のチュートリアルを試した環境を書いておきます。
- Windows 10 Pro 64bit, 1703 (Creators Update)
- Bash on Ubuntu on Windows, 16.04.2LTS
- sspa のリポジトリは 2017/7/31 現在のものをfork
- 書籍:初版第1刷(2017/6/26発行)
BoW 便利ですねー。何の(大きな)問題もなく試せました。
第1章
p.12, sspa スクリプトが失敗する
で、早速、勇んで始めたら、第1書 p.12 のsspaスクリプトの実行で早速躓いてしまいました。
mor@DESKTOP-RLA4CF1:~/learnjs$ ./sspa server Can't find Python. You need Python 2.7 or later to use this.
調べてみると、sspa スクリプトはpython2.7以上が必要とあり、スクリプト内部では python コマンドで呼び出していました。
で、Bash on Ubuntu on Windows の場合 python3 がデフォルトで入っているのですが、pythonコマンドでは起動できない(python3で呼び出すようです)。 なので、デフォルト状態では、pythonがないと判断されて実行できないとうおちでした。
解決方法は、シンボリックリンクを作成するだけです。
cd /usr/bin sudo ln -s python3 python
これで無事に動きました。
p.17, AWS CLI のセットアップ
BoW なら Ubuntu と同じようにすれば、本のとおりでも(たぶん)うまくいくと思いますが、
sudo apt-get install python-pip
で pip をインストールしてみると、 python2.x に関連するパッケージもインストールされてしまい、気持ち悪かったです。
なので、今回は AWS CLI のインストールガイドのページを参考にインストールスクリプトをダウンロードし
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" sudo python ./get-pip.py sudo pip install awscli
としました(sudo経由でルート権限で実行すると、ホームディレクトリではなく、システム全体にpipをインストールしてくれる)。
ちなみに、同じawsの説明ページでもこちらにある手順だと、ユーザー環境へのインストールになるのでご注意を(場合によってはこれのほうがいい場合もあるかな)。
p.19, AWS CLI のセットアップ
リージョンとして ap-northeast-1 を指定してみましたが、以後特に問題なく使うことができました。 第6章だったかな?コード内にリージョン名を記入するときには、自分が指定したものを使うようにします。
第3章
テストの書き方がわからない
第3章以降は本文中ではテストの書き方に言及しなくなっています(まあ、紙面の都合上仕方ないでしょうね)。 なので、著者のサイト の Resources からソースコード一式をダウンロードして、自分が今読んでるソースコード(learnjs/xxxx/public.app.js)に該当する番号(章・節番号を元にしているようです)内のテストコードなどを参考に、自分で書いて試すが一番よさそうです。
p.57, コード
マイナス記号が抜けてます。オライリーの正誤表のページにも記載されています。
p.58, 3.2.2『データモデルを成長させる』
個人的には、この節のようにすればフォーマッターを適用できますよ、という紹介のように受け取れました。 ダウンロードしたコードにも実装がないようですしね。
あれか?自分でフォーマット用のコードを書けばすんなりチュートリアルとして使えるということなのかな? いずれにしても、チュートリアルとしては若干補う必要があるので、場合によっては飛ばすのもありかと思います。
第4章
p.84, 4.2.1 節
./sspa create_pool conf/cognito/identity_pools/learnjs
このまま実行すると
support/jsed.py
でエラーになります。
理由は、support/jsed.py 16行目の print 文がpython3 に対応していないためです。
ということで、ここを print関数に修正します。
mor@DESKTOP-RLA4CF1:~/learnjs/support$ diff jsed.py.org jsed.py 16c16 < print search(doc, keys) --- > print( search(doc, keys) ) mor@DESKTOP-RLA4CF1:~/learnjs/support$
diff とるとこんな感じです。
p.91, refresh 関数はどこで使う?
ここを読んだだけでは、このrefresh 関数の使われ方がわからなくて、しばし戸惑ってしまいました。 読み進めると、p.113 の 5.4.1『ファイルセーフのデータアクセス関数』のところで、実際に使われており、なるほどと納得できました。 第4章では使われてないので、気にせず読み進めるのが良いのではないか?と思います。
第6章
Node.js のセットアップはどうする?
下記の記事にまとまっているように、
http://qiita.com/7tsuno/items/e666223d6be22d657542qiita.com
BoW の場合 (付録でも触れらている) nvm を利用するのが良さそうです。
インストール方法は、 nvm のサイトに書いてあるので、それに従います。
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
一度ログアウトして再ログイン後、インストールします。
nvm install 4.3
※ node.jsのバージョンは 4.3 にしました。今だと、Lambda が 6.10 もサポートしているのですが sspa スクリプト内で固定的に 4.3 が指定されているので、それに合わせています。
ちなみに、最初はrootユーザーでインストールしようとしたんだけど、
このコメントに『rootでインストールして共有して使うのを想定していない』とあったので、やめました。
細かいですが、BoWだと zip もなかったので、ついでにインストールしておきます。
sudo apt-get install zip
p125, 6.1.3 のLambdaの課金単位
本文中では『GB/秒』とかいてあるけど、AWS Lambda の料金のページ等を見ると『GB・秒』の表記のほうが正しいと思います。
6.3の『プロジェクト』と6.3.1の『プロジェクト』は同じ意味?
確証はないですが、個人的には多分違うんじゃないかなと思ってます。 6.3節では『プロジェクト』がモノリシックな状態を作り出すものを意味しているのに対して、6.3.1節ではプロジェクトがLambda関数をまとめるファイルのような意味合いで使われているように思われます。
こういう時、原著を読むと分かったりするのですが、実際のところどうなんでしょうかね?
p137, このコラムはどうやって試せばよい?
自分でCLIで試すということじゃないかと思ってます。試したのをメモとして残しておきます。
p.138 の popularAnswers で dynamoDB を使った処理
mor@DESKTOP-RLA4CF1:~/learnjs$ aws --profile admin dynamodb scan --table-name "learnjs" --filter-expression "problemId = :probId" --expression-attribute-values '{":probId": {"N":"1"}}' { "ScannedCount": 2, "Count": 1, "ConsumedCapacity": null, "Items": [ { "userId": { "S": "ap-northeast-1:abcdefgh-1234-5678-ijkl-xxxxxxxxxxxx" }, "problemId": { "N": "1" }, "answer": { "S": "true" } } ] } mor@DESKTOP-RLA4CF1:~/learnjs$
lambda関数呼び出しの場合
mor@DESKTOP-RLA4CF1:~/learnjs/public$ aws --profile admin lambda invoke --function-name 'popularAnswers' --payload '{"problemNumber": 1}' tmp { "StatusCode": 200 } mor@DESKTOP-RLA4CF1:~/learnjs/public$ cat tmp {"true":1}
p138, コード中の リージョン名
第1章で指定したリージョンに合わせて編集しましょう。
p138, コード中の byCount 関数
引き算のマイナスが抜けているみたい。たぶんこうなるはず。
function byCount(e1, e2) { return e2[0] - e1[0]; }
p.139, 6.4がチュートリアルとして扱いにくい
仕方ないのかなー?
6.3.4のビューおよびp.140のポリシーのサンプルなどもあると分かり易いんだけど、これがないので、若干試しにくいんだと思います(原著者のサイトやダウンロードしたものにも含まれてないようですしね)。
ということで、簡単ですが自分で試してみました。 popularAnswer はログインしており、かつ、解答が正解した場合に、次の問題へのリンクの下に、『Show Popular Answer』というリンクが表示され、それをクリックすると、その問題について多い正解が表示されるという形にしました。
popularAnswerのビューの例
public/index.html にビュー(popular-view)をtemplatesに追加します
<div class='popular-view'> <h3 class='title'></h3> <div class='popular-list'></div> </div>
また、リンク表示用の要素も追加します。
<div class='popular-link'> <a>Show popular answer</a> </div>
popularAnswer 表示処理
public/app.js に addPopularLink 関数および popularView 関数を追加します。
learnjs.addPopularLink = function(parentElem, problemNumber) { var link = learnjs.template('popular-link'); link.find('a').attr('href', '#popular-' + problemNumber); parentElem.append(link); }
learnjs.popularView = function(data) { var problemNumber = parseInt(data, 10); var view = learnjs.template('popular-view'); var popular = view.find('.popular-list'); learnjs.popularAnswers(problemNumber).then(function(items) { var objs = JSON.parse(items.Payload); for (var key in objs) { popular.append($('<p>').text('answer: ' + key + ', count: ' + objs[key])); } }); view.find('.title').text('Popular Answer for Problem #' + problemNumber); return view; }
既存の buildCorrectFlash 関数を修正して、認証が通っている場合に、リンクを追加するようにします。
learnjs.buildCorrectFlash = function(problemNum) { var correctFlash = learnjs.template('correct-flash'); var link = correctFlash.find('a'); if (problemNum < learnjs.problems.length) { link.attr('href', '#problem-' + (problemNum+1)); } else { link.attr('href', ''); link.text("You're Finished!"); } learnjs.identity.done(function() { learnjs.addPopularLink(correctFlash, problemNum); }); return correctFlash; }
showView 関数に popularView を表示するためのルートを追加します。
learnjs.showView = function(hash) { var routes = { '#problem': learnjs.problemView, '#profile': learnjs.profileView, '#popular': learnjs.popularView, '#': learnjs.landingView, '': learnjs.landingView }; var hashParts = hash.split('-'); var viewFn = routes[hashParts[0]]; if (viewFn) { learnjs.triggerEvent('removingView', []); $('.view-container').empty().append(viewFn(hashParts[1])); } }
Lamda ポリシーの追加
p.140 にあるようにCognitoロールにLambda関数の呼び出しを許可するポリシーを追加します。 今回はコンソールで実行しました。
ブラウザで AWS のコンソールにログインして、 IAM -> ロール -> learnjs_cognito_authenticated を選択します 『インラインポリシー』の『ロールポリシーの作成』をクリックします。 今回は、Policy Generator を使い、下記の様なポリシーを追加しました。
(Lambda関数の呼び出し, invoke を許可するポリシー)
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1502980684000", "Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": [ Lambda関数のARN ] } ] }
一応、これで手元の環境ではうまく動きました。 テストファーストを押しているチュートリアルですが、テストには手が回りませんでした・・・
p.140, FunctionName は 'learnjs_popularAnswers' ではなく 'popularAnswers' では?
Lambda の設定内容次第ですが、本文に従っているなら、多分間違いだと思います(popularAnswers が正しいと思います)。
第7章
p.152, 7.3.2 を実装したが、どうテストを書けばよい?
Webワーカーを導入した場合、テストも修正しないといけないのですが、ダウンロードしたコードには変更が反映されていないようです。 自分で書かないといけないのですが・・・(略)。
p.161, 7.6.1 にある CloudFront を試したい
ということで、CloudFront を試してみました。CloudFront の一般的な話は下記などを参考にしてください。
設定方法自体は少しネットを検索すれば出てくると思うので、詳しくは触れませんが、S3のバケット名を指定して、下記画像のように独自ドメイン名を指定することぐらいで設定完了でした。
その他として、この本で作っているアプリで扱うときは、
- ドメイン名(/終わり)でアクセスするには Default Root Object に index.html などを指定しておく
とよいかと思います。(細かい設定は全然見てませんが)予想以上に、この本で使ったS3の公開用バケットに対して CloudFront を設定して公開するのは簡単にできるな、と思いました。
Restrict Bucket Access の設定方法について
本文中でも直接S3のバケットにアクセスできないように設定しましょう、とあるので試してみました。
設定手順について簡単に示すとだいたい次の通りになります。
- 作成した distribution を選択し、Origins タブを表示し、 Edit を実行
- Restrict Bucket Access を Yes にする
- Origin Access Identity, Comments, Grant Read Permissions on Bucket が表示される
- Origin Access Identity は Create a New Identity を選択
- Comments は適当に
- Grant Read Permissions on Bucket は Yes, Update Bucket Policy を選択
- Yes, Edit ボタンを押して、変更を有効にする
ちなみに、Origin Access identity はCloudFrontからS3へアクセスする際のIdentityを新たに作るか既存のものを利用するかを指定するものです。 新規の場合は、Createでよいらしい。
Grant Read Permissions on Bucket は S3 のバケットポリシーを自動で編集するために読み取り許可を与えるか否かを指定するものです。Noを選んだ場合は、自分でS3のバケットポリシーを編集しないといけないので、Yesを選択したほうが楽なようです。
なお、Restrict Bucket Access の設定方法は下記記事を参考にさせていただきました。
S3の特定バケットへのアクセスを特定のCloudFrontからのみ許可する。 - Qiita
AngularJSで作ったSPAをAWS上の「S3+CloudFront」でお手軽ホスティングして、クラウドサービスってやっぱ素晴らしいなと思った話 | I am mitsuruog
オリジンアクセスアイデンティティを使用して Amazon S3 コンテンツへのアクセスを制限する - Amazon CloudFront
CloudFront 経由のみのアクセスに限定?
で、これで、S3で公開しているURLではアクセスできないかと思いきや、全然できてしまいます。 なんでだ?
というので調べてみると、sspaスクリプトがS3にアップロードする際に、ACLとして public-read を与えていたためのようです。これは、S3 の static web hosting を行う際には、誰でも読み込み権限を与える必要があるために、わざわざ設定しているようです(AWS のドキュメントにも説明がありますね)。
ちなみに、ネットでS3の静的ホスティングの記事などを見ると、バケットポリシーを設定するという記事(例えば、これとか)などが良くありますが、これも同じことで、ACLで指定するか、バケットポリシーで指定するかの違いだけのようです。
なので、CloudFront 経由のみにアクセスを限定したい場合は、上記の Restrict Bucket Access に加えて、public-readのACLを削除する必要があります。 やり方はいろいろあると思いますが、今回でしたら、一旦公開バケットの中のファイルをすべて削除して、コマンドラインから
aws --profile admin s3 sync public/ s3://learnjsバケット名 --acl private
のように private で再アップロードすればOKです。
なお、ついでに、 static web hosting も無効にしておきます(有効でも公開されてないのでアクセスできないのですが)。 これで、CloudFront 経由のアクセスのみになりました。
最後に
あれこれ書きましたが、一言でいうなら、おすすめできる本です。
若干チュートリアルとして扱いにくいところもありますが、まあ、新しいものに触れる際は、自分で試行錯誤しないといけないので、ま、その訓練と思えばいいんじゃないでしょうか。
改訂されることがあれば、チュートリアルとしてより扱いやすい本になることを期待しています!
にしても、AWS 使えば使うほど、amazon にベンダーロックインされてる感がすごいなー。まさにこの本の第一章で書かれている状態に近づきつつあるや。ま、これはこれとして楽しみましょう。