[2017夏版] AWS Start-up ゼミ参考資料リンク集をマークダウンに起こしました

TL;DR

こんにちは。AWSリハビリ中のnntsuguです。

[AWS Start-up ゼミ] よくある課題を一気に解説!〜御社の技術レベルがアップする 2017 夏期講習〜

先日参加させていただいたAWS Stat-upゼミ、講師のSA塚田さんの資料がとても良かった。
AWS上でサービスを構築運用する上での勘所がユースケースベースで整理されていて、モヤモヤしていた部分がかなりスッキリししました。

資料内にある参考資料や動画へのLink、とても勉強になるのですが、

  • PDFやSlideShareだとスマホから参照しづらい
  • 未読管理をしやすくするため

マークアップに起こしました。

毎朝ジムで走りながら参考資料の動画を見て聞いています。とても捗ります。

参考資料は主にBlack Beltの資料&動画アーカイブ、AWS Summit/Dev Dayの資料で構成されています。

ユーザ動向を分析したい

CI/CDをちゃんとしたい

コンテナを使いたい

運用監視ちゃんとしたい

システム負荷下げたい

(モバイルアプリの)Growth Hackしたい

コスト下げたい

  • AWS Black Belt Online Seminar資料&動画

    • クラウドのためのアーキテクチャ設計-ベストプラクティス-(資料|動画)
    • Auto Scaling (資料 | 動画)
    • Amazon EC2 Spot Instances (資料 | 動画)
    • サーバーレスによるアーキテクチャパターンのご紹介 (資料 | 動画)
  • AWS Summit/Dev Day講演資料 (2016 | 2017)
    • AWS のコスト最適化入門 (2017)(資料 | 動画)
    • [インティメート・マージャー様] AWS Summit 2017 講演資料 Amazon ECS と SpotFleet を活用した低コストでスケーラブルなジョ
      ブワーカーシステム(資料 | 動画)
    • AWS Well-Architected フレームワークによるクラウド ベスト プラク
      ティス (2017) (資料 | 動画)

その他

IPOとBuy Out、デューデリジェンス

続きを読む

http_proxyの設定があるとlocalstackの起動でエラーになる

localstack を起動するときに、環境変数 http_proxy (または HTTP_PROXY) が設定されている環境では、以下のように elasticsearch の起動チェックで、何度か WARNING:infra.py:Service “elasticsearch” not yet available, retrying… の警告が出た後、エラーで終了することがある。

WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
ERROR:localstack.services.es.es_starter:Elasticsearch health check failed (retrying...): (以降略)

また、Docker on Mac でもデフォルトがsytsem proxyを参照するようになっているため、OSのネットワーク設定でプロキシが有効になっている場合でも、同様の問題が発生する。

WARNING:infra.pyc:Service "kinesis" not yet available, retrying...
ERROR:infra.pyc:Error checking state of local environment (after some retries): Traceback (most recent call last):
File "/opt/code/localstack/localstack/services/infra.py", line 285, in check_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'
Traceback (most recent call last):
File "bin/localstack", line 79, in <module>
infra.start_infra()
File "/opt/code/localstack/localstack/services/infra.py", line 359, in start_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'

これは、起動チェックでモックサーバに接続しに行くところがプロキシサーバ経由となり、モックサーバに接続できないことが原因である。
このような場合、プロキシサーバを使用しないように環境変数の設定を解除すればエラーは解消するはずである。あるいは、環境変数 no_proxy
no_proxy=<コンテナのhostname>,localhost,127.0.0.1,[::1]
のようにセットすれば、ヘルスチェックがプロキシをバイパスするようになる。

この問題は、#239で解決済みであり、リポジトリから最新版を落としてくるか、PR#244のパッチを適用することで解消する。

続きを読む

aws-sam-localだって!?これは試さざるを得ない!

2017-08-11にaws-sam-localのベータ版がリリースされました。
単に「早速試したで!」と言う記事を書いても良かったのですが、少し趣向を変えてサーバレス界隈の開発環境のこれまでの推移を語った上で、aws-sam-localの使ってみた感想もお話しようかと思います。

サーバレス開発環境の今昔

サーバレス自体がかなり最近になって生まれた風潮なので昔とか言うのも問題はあるかと思いますが、とにかくサーバレスなるものの開発環境について私にわかる範囲でお話しようと思います。誤りや不正確な点については編集リクエストやコメントを頂けると幸いです。

なお、サーバレスという言葉は一般的な名詞ですが、私がAWS上でしかサーバレスに触れていないため、AzureやGCPなどには触れず、もっぱらLambdaの話になってしまうことをあらかじめご了承ください。

Lambdaのデプロイは辛かった

Lambdaの基本的なデプロイ方法はZIPで固めてアップロードです。
直接ZIPをLambdaに送るか、あらかじめS3に置いておいてLambdaにはそのURLを教えるかといった選択肢はありましたが、手動でZIPに固めてAWSに送るという手順は不可避でした。なんかもうすでに辛い。

さらに言うとLambdaに送りつけられるのはLambdaで実行するコードだけ。性質上Lambdaは単体で使われることはほとんどなく、他のサービスと連携することがほとんどなのにその辺は自分で管理するしかありませんでした。辛い。

CloudFormationで管理することは可能でしたが、CloudFormationテンプレートを書くのがかなりダルいことと、CloudFormationの更新とZIPのアップロードを別途行う必要があって手順が煩雑化しやすいため、「もうええわ」と手動管理してることが多かったと思われます。

また、ローカル環境で実行するには一工夫必要でした。

颯爽登場!Serverlessフレームワーク

そんな時に颯爽と現れたのがServerlessフレームワークでした。
ServerlessフレームワークにおいてはLambdaファンクション及び関連するリソースを独自のyamlファイルで管理します。結局は一度CloudFormationテンプレートに変換されるのですが、CloudFormationテンプレートよりも単純な形式で記述できたのが流行った一因かと思います。また、sls deployコマンドでLambdaのコードのアップロードおCloudFormationスタックの更新を一括で行ってくれたため、デプロイの手順は従来よりもはるかに簡略化されたかと思われます。

Lambdaテストしづらい問題

デプロイに関する問題はServerlessフレームワークや、ほぼ同時期に現れたSAMによって改善されましたが、開発プロセスにおいて大きな課題がありました。

テストし辛ぇ…

上記の通りLambdaは性質上他のサービスと連携することが多いため、その辺をローカル環境でどうテストするかに多くの開発者が頭を抱えました。対策として

  1. モッククラスを作って、実際のサービスのような振る舞いをさせる
  2. プロダクションとは別のリージョンに環境を再現して、そこで実行する

といった方法がありましたが、それぞれ

  1. モッククラスの実装がすこぶるダルい 下手したらロジック本体より時間かかる
  2. クラウドにデプロイしないとテストできないため、時間がかかる

といったデメリットがありました。

LocalStackとaws-sam-local

サーバレス開発者の嘆きを聞いたAtlassianがローカル環境でAWSのサービスのエンドポイントを再現するなんとも素敵なツールを作り上げました。それがLocalStackです。
再現されているサービスの数が物足りなく感じられたり、サードパーティ製であることに一抹の不安を覚えたりする人もいるかと思いますが、これ以上を求めるなら自分で作るぐらいしかないのが現状かと思います。

そしてaws-sam-local。こちらはLocalStackと少し趣が異なります。LocalStackが連携するサービスのエンドポイントを再現して提供するのに対して、aws-sam-localは実行環境の提供という意味合いが強いです。そして重要なことはAWSの公式がサポートしているということです。公式がサポートしているということです。大事なことなので(ry
「実行するのはローカルでNode.jsなりPythonなりで動かせばええやん」と思いがちですが、ランタイムのバージョンなどを本番環境と確実に揃えられるのは大きな利点です。
まだベータ版が出たばっかなので今後に期待といったところでしょう

aws-sam-local触ってみた

それでは実際に触ってみましょう。
ちなみに当方環境は

  • OS: macOS Sierra 10.12.6
  • Docker for Mac: 17.06.0-ce-mac19

です。

事前準備とインストール

公式のInstallationの項目を読み進めますが、事前にDockerを使えるようにしておく必要があります。

Macだったら普通にDocker For Macをインストールしておけば問題ありません。
一方Windowsだと

スクリーンショット 2017-08-16 14.48.18.png

まさかのDocker Toolbox推奨。 Docker For Windowsェ…
そしてaws-sam-localのインストールですが、私は-gオプション排斥論者なのでローカルインストールします

npm install aws-sam-local

実装

今回はこちらを参考にAPIゲートウェイから呼び出すLambdaを実装します。
ほぼ丸パクリですが一部アレンジしてますのでソースものっけます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Local test
Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

ランタイムをnodejs6.10に変更してます。
新しく作る場合にわざわざ古いバージョンを使う必要もありませんので。

余談ですが、WebStormのCloudFormation用のプラグインは今の所SAMには対応してないのか、Type: AWS::Serverless::Functionのところにめっちゃ赤線を引かれます。

index.js
/**
 * Created by yuhomiki on 2017/08/16.
 */

"use strict";

const os = require("os");
console.log("Loading function");


const handler = (event, context, callback) => {
  return callback(null, {
    statusCode: 200,
    headers: { "x-custom-header" : "my custom header value" },
    body: "Hello " + event.body + os.EOL
  });
};

exports.handler = handler;

完全に書き方の趣味の問題です。
内容は参考ページのものと全く同じです。

package.json
{
  "name": "sam_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "invoke-local": "sam local invoke HelloWorld -e event.json",
    "validate": "sam validate",
    "api-local": "sam local start-api"
  },
  "author": "Mic-U",
  "license": "MIT",
  "dependencies": {
    "aws-sam-local": "^0.1.0"
  }
}

aws-sam-localをローカルインストールしているので、package.jsonのscriptsに追記しています。

実行

それでは実行してみましょう

上記のpackage.jsonに記載した

  • invoke-local
  • validate
  • api-local

を実行していきます。

invoke-local

Lambdaファンクションをローカル環境で実行します。
Lambdaファンクションに渡すevent変数はワンライナーで定義することも可能ですが、あらかじめJSONファイルを作っといた方が取り回しがいいです。

json.event.json
{
  "body": "MIC"
}

実行結果はこんな感じ

スクリーンショット 2017-08-16 15.25.42.png

まず最初にdocker pullしてランタイムに応じたDockerイメージをダウンロードします。
その後はコンテナ内でLambdaファンクションを実行し、最後にcallbackに与えた引数を出力といった流れです。
ログの形式がすごくLambdaですね。あとタイムゾーンもUTCになっていますね。
メモリの使用量をローカルで確認できるのは嬉しいですね。

-dオプションをつけることでデバッグもできるようです。
公式のgithubにはご丁寧にVSCodeでデバッグしてる様子がgifで上げられてます。

validate

テンプレートファイルのチェックをします。
デフォルトではカレントディレクトリのtemplate.yamlファイルをチェックしますが、-tオプションで変更することが可能です。

失敗するとこんな感じに怒られます。

スクリーンショット 2017-08-16 15.33.47.png

成功した時は「Valid!」とだけ言ってきます。きっと必要以上に他人に関わりたくないタイプなのでしょう。

api-local

sam local start-apiコマンドはローカル環境にAPIサーバを立ち上げます。
ホットリロード機能がついてるので、立ち上げっぱなしでもソースを修正したら自動で反映されます。いい感じですね。

スクリーンショット 2017-08-16 15.40.58.png

立ち上がるとこんなメッセージが出るので、あとはCURLなりPostManなりで煮るなり焼くなり好きにしましょう。

CURLの結果はこんな感じ
スクリーンショット 2017-08-16 15.51.39.png

所感

Lambdaのローカル実行環境を公式が用意したことに大きな意義があるかと思います。
Dockerさえあればすぐに使えることと、SAMテンプレートを書かざるをえないのでInfrastructure as Codeが自然と根付いていくのも個人的には好感を持てます。

ただし、まだベータ版なこともあって機能的にもの足りない部分があるのも事実です。
具体的にはやはりDynamoDBとかもテンプレートから読み取ってDockerコンテナで用意してくれたらなーと思います。LocalStackやDynamoDB Localでできないこともないでしょうが、DynamoDB Localに関してはテンプレートからテーブル作ったりするの多分無理なのでマイグレーション用のコードを書くことになりますし、LocalStackに関しては実はあまり真面目に使ったことないのでわかりませんが環境構築に一手間かかりそう。ていうかできれば一つのツールで完結させたい。

SAMしかりaws-sam-localしかり、AWS側としてもより開発がしやすくなるような環境づくりをしていくような姿勢を見せているので、今後のアップデートに期待したいところですね。

続きを読む

localstackをdockerを使ってセットアップする

AWSを使って開発するときに、開発環境はローカルに配置して気軽に実行したいので、LocalStackを構築してみました。起動方法はいくつかるようですが、すでにデータベースなどをdockerで立ち上げているので、dockerを使ってLocalStackを動かします。

インストール

ドキュメントにはgitのリポジトリをローカルにcloneするように書いてありますが、リポジトリ全体が必要はなく、docker-compose.ymlさえあればいいので、それをローカルにコピーします。本当に必要なのは、そのファイルの中のservicesのlocalstackのところだけですね。

起動

docker-compose up -d

で起動します。Macの場合は

TMPDIR=/private$TMPDIR docker-compose up -d

とします。

AWSのコマンドのインストール

ついでにawsのコマンドをインストールします。pyenvとpyenv-vertialenvがインストールされている前提で話します。

pyenv virtualenv 3.6.2 aws
pyenv shell aws

次にawsの設定を行います。

aws configure

これで4つの情報を聞いてくるので、こんな感じで入力します。

AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-east-1
Default output format [None]: json

これを設定しておかないと動かないので、適当に入力しておけばいいみたいです。

動作確認

あとは、「LocalStackをつかってローカルでLambdaを実行してみた」に書いてあるように、Lambdaを動かして動作確認します。

おわり

これで動作確認が終わりです。でも、ダッシュボードが8080のポートを使っていますが、dockerのイメージをそのまま使っている限りだと、このポートを変更することはできないようです。自分の開発中のものが8080を使っていると悲しいですね。

続きを読む

localstack を Docker on Mac 上で動かす

Docker on Mac のインストール

https://docs.docker.com/docker-for-mac/install/ から入手できます。
このドキュメントにしたがって、インストールします。

localstack パッケージの更新

pip でインストールしたパッケージは少し前まで、docker イメージが atlassianlabs/localstack になっていましたが、最近になって localstack/localstack に変更されています。

localstack パッケージは 最新版に更新しておいた方がよいでしょう。

pip install -U localstack

localstack を Docker on Mac 上で起動

Docker on Mac が動くようになったら、localstack--docker オプションをつけて起動します。
このとき、Docker for Mac の設定の Proxies のところで No proxy を選択して、プロキシを使用にないようにしておくことを推奨します。

$ localstack start --docker

docker イメージを自動的にダウンロードして、コンテナが起動されます。

Ready. が表示されれば、ローカル起動と同じように使用できます。

docker run で起動していますので、Ctrl-P Ctrl-Q でデタッチできます。

停止するときは、docker ps -a でコンテナIDを確認して、

docker stop コンテナID

で停止します。

再開するときは、

docker restart コンテナID

です。

localstack start --docker を実行すると、もうひとつ新しいコンテナを作ってしまいます。

WARNING:infra.pyc:Service "kinesis" not yet available, retrying... のあとエラーになる場合

Docker コンテナで起動したとき、次のようなエラーが出ることがあります。

WARNING:infra.pyc:Service "kinesis" not yet available, retrying...
ERROR:infra.pyc:Error checking state of local environment (after some retries): Traceback (most recent call last):
File "/opt/code/localstack/localstack/services/infra.py", line 285, in check_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'
Traceback (most recent call last):
File "bin/localstack", line 79, in <module>
infra.start_infra()
File "/opt/code/localstack/localstack/services/infra.py", line 359, in start_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'

このようなときは Docker for Mac の設定の Proxies のところで No proxy を選択して、プロキシを使用にないようにしてみてください。

プロキシを使う設定なっていると、Docker 内で起動した localstack がモックサーバの起動チェックをするときに、プロキシ経由でアクセスして、チェックに失敗することがあります。

続きを読む

サーバーレスの入門に!自宅サーバーレス+自宅S3環境で作るサーバーレス・サムネイルサーバー!

サーバーレスのHelloWorld = サムネイルサーバー?

日本語で読めるサーバーレスの有名記事といえば,伊藤直也さんの一休の事例だと思います.
下記の記事では,Amazon Lambdaを使って,一休のサムネイルサーバーを構築した事例の紹介をしています.

伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(前編)。QCon Tokyo 2016
伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(中編)。QCon Tokyo 2016
伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(後編)。QCon Tokyo 2016

スケールしやすい.安価にサービスできる.ということでサーバーレス・アーキテクチャは注目が集まっているようです.

そこで,今回,一休での事例をまねて,

1時間で作れる!かんたん自宅Serverless環境! ~はじめてのServerless Application入門~

で作った,Serverless環境のiron-functionsと,

コマンド1つで作れる!かんたん自宅Amazon S3互換環境!

で作った,S3互換環境を合わせて,サーバーレス・サムネイルサーバーを構築したいと思います.

サーバーレス・サムネイルサーバーのシークエンス

image.png

ざっくりとした概要の図です.
1. リクエストから「ファイル名」と「サイズ」を受け取る
2. minioのthumbsのバケットに該当のサイズのサムネイルがあるか?ない場合は3.へ.ある場合は7.へ.
3. minioのimagesのバケットからファイルをダウンロード
4. ダウンロードした画像ファイルをリサイズ
5. リサイズした画像をminioのthumbsのバケットへアップロード
6. リサイズした画像をレスポンスし,終了
7. minioのthumbsのバケットからサムネイルをダウンロード
8. ダウンロードした画像をレスポンスし,終了

API仕様書

(仕様書というほどではないですが・・・)

概要

png画像のサムネイルを提供する.

  • 指定できるサイズは画像の横のサイズのみ.
  • 縦横比は保持する.
  • png以外の画像フォーマットには非対応.
  • テストのため,エラーハンドリングは考慮しない

エントリポイント

/iron_thumbs/thumbs

URLパラメーター

変数 説明
filename imagesにアップロードされたファイル名(key)
size サムネイルの画像の横のサイズ

レスポンス

png画像データ

http://localhost:8000/iron_thumbs/thumbs?filename=test.jpg&size=30

minioのimagesバケットに入っているtest.pngを,画像の横のサイズが30pxになるように縮小し,バイナリデータとして出力する.

事前準備

今回,

  • iron-functions
  • minio

という2つのソフトウェアを使います.
手前みそですが,Seveless環境のiron-functionsは

1時間で作れる!かんたん自宅Serverless環境! ~はじめてのServerless Application入門~

の記事で紹介しています.
また,S3互換環境であるminioに関しては,

コマンド1つで作れる!かんたん自宅Amazon S3互換環境!

の記事で紹介しています.
今回,サムネイルサーバーを構築するにあたって,最低限必要なものとして,

  • docker
  • awscli
  • glide

の3種類です.glideは上記記事では,取り扱っていませんが,go言語でライブラリをインストールするためのパッケージとして今回必要です.
あとgolangの開発環境があるとよいですが,今回,動かすだけであれば必要ないと思います.(おそらく,glideのインストールと同時にgo言語の開発環境も入ると思います)

ソースコード

今回,コードが300行程度あったため,githubのほうに置きました.
https://github.com/kotauchisunsun/iron_thumbs.git

デプロイ

最初にMinio(S3互換サーバー)を前回と同じ設定で8080ポートでサービスします.

設定する変数
Access Key AKIAIOSFODNN7EXAMPLE
Secret Key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# minioを起動
$ sudo docker run --rm -p 8080:9000 -it 
    -e MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
    -e MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    minio/minio server /export

その次に,minioにバケットを作成します.

# minioへimagesバケットの作成
$ aws --endpoint-url http://192.168.0.6:8080 s3 mb s3://images
# minioへthumbsバケットの作成
$ aws --endpoint-url http://192.168.0.6:8080 s3 mb s3://thumbs

iron-functionsを8000ポートでサービスします.

# iron-functionsのサービス
$ sudo docker run --rm -it --name functions -v ${PWD}/data:/app/data 
                   -v /var/run/docker.sock:/var/run/docker.sock 
                   -p 8000:8080 iron/functions

サムネイルサーバーをサービスします.一応Dockerコンテナの流儀のようなものを意識して,各種サービスに必要なパラメーターは環境変数として渡しています.

# ソースコードをクローン
$ git clone https://github.com/kotauchisunsun/iron_thumbs.git
$ cd iron_thumbs
# パッケージの依存関係を解消
$ glide update
$ sudo fn build
$ fn apps create iron_thumbs 
    --config IMAGE_BUCKET=images
    --config THUMB_BUCKET=thumbs
    --config S3_ENDPOINT=http://192.168.0.6:8080
    --config S3_REGION=us-east-1
    --config S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
    --config S3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ fn routes create iron_thumbs /thumbs

今回,テストデータとして,いらすとやの画像を使います.それっぽい画像としてクラウドコンピューティングの画像を使います.
image.png

# 素材をダウンロード
$ wget "http://4.bp.blogspot.com/-dVbfTZcofUU/VGX8crT5GiI/AAAAAAAApG4/CB7GF5UmMqE/s400/computer_cloud_system.png"
# minioにファイルをアップロード
$ aws --endpoint-url http://192.168.0.6:8080 s3 cp computer_cloud_system.png s3://images/

実際にiron_thumbsに対してリクエストしてみます.

$ curl "http://localhost:8000/r/iron_thumbs/thumbs?filename=computer_cloud_system.png&size=180" > thumbs.png

これでサムネイルが出来ています!

ベンチマーク

「いらすとや」ベンチ

今回もベンチマークを取ってみました.

どのようなものを想定したかというと,「いらすとや」を自分で運用すると考えました.
image.png

「いらすとや」で画像を検索すると,検索にマッチした画像のサムネイル(赤枠の部分)が表示されます.この画像の横のサイズが約180pxになっています.(実際は少し違います)
このサムネイルをいかに速く作れるのか?をベンチマークしてみたいと思います.

使ったベンチマークソフトは以前も使ったApacheBench.設定は,

$ ab -n 1000 -c 10 "http://localhost:8000/r/iron_thumbs/thumbs?filename=computer_cloud_system.png&size=180"

こんな感じ.
今回,2種類のベンチマークをしました.

always_make_thumbs

これはリクエストごとに常にサムネイルを生成し,レスポンスします(ソースコードを改変してます.)

cached_thumbs

これはthumbsのバケットからサムネイルを取ってきて,レスポンスします.

サムネイルサーバーのシステム設計をする

色々なサムネイルサーバーの話がネットには転がっています.

pixivのサムネイル事情(pixiv)
料理を楽しくする画像配信システム(cookpad)

このように各社各社がしのぎを削ってるようです.
今回,このようなサムネイルサーバーの構成を考えてみました.

image.png

実運用されているサムネイルサーバーは,サムネイルの動的生成だけでなく,CDNが前に立つことがよくあるようです.
理由は主に2つあって,

  1. サムネイルサーバーの負荷軽減
  2. 高速なサムネイルの配布

です.サムネイル作成はCPUへの負荷が高く,サーバーが高負荷になりやすいです.では,サーバーを追加すればよいか?という話になるのですが,お金のないベンチャーはそういうことが出来ません.そのため,出来るだけCDNでサムネイルをキャッシュし,なおかつアクセスのある時は,サーバーレスで短時間動かすことで,安値でスケールしやすいシステムを作ることが出来ます.
実はこの辺の,サムネサーバーの要求要件が,かっちりサーバーレスのアーキテクチャにはまっているため,事例として,よく取りあげられてるのだろうなぁ.と思っています.

なのですが,

ぶっちゃけCDNとサムネサーバーとS3の面倒を見るのはめんどくさいです

キャッシュというのは,高速化には常套手段ではありますが,個人的には,バグを引き起こす原因の上位に来てます.(サーバーレスアーキテクチャだけど,すごくステートフルだし)
実際に,最近,メルカリではCDNの周りで個人流出事件を起こしてます(CDN切り替え作業における、Web版メルカリの個人情報流出の原因につきまして)
そのような理由で,個人的にはキャッシュは出来るだけ,使いたくない気持ちではあります.
しかし,今回考えたサムネサーバーの構成は,先ほどの図にあるように2段階にキャッシュを持っています.

  1. CDN
  2. サムネイル

CDNにサムネイルがなければ,サムネサーバーに問い合わせ,サムネサーバーがサムネイルを今までに作ったことがなかった場合,画像ファイルをダウンロードしてきて,サムネイルを作ります.

そのため,今回は,出来るだけキャッシュを少なくするために,「どこかのサーバーを省略できないかな?」というのが,このベンチの目的です.
そのため,

  • CDNを使わないバージョン=cached_thumbs
  • サムネを動的に作り続けるバージョン=always_make_thumbs

と考えて,ベンチを行ってみました.

結果・考察

項目 平均レスポンス時間[ms]
always_make_thumbs 8417.722
cached_thumbs 7542.938

一度サムネをS3(minio)にキャッシュする方が,1,000[ms]程度速く,7,500[ms]でサムネイルをサービスすることが出来ました

ただ50歩100歩で,普通にWebページを見ていて,7.5秒もサムネの表示に時間がかかってたら使い物にならない感じです.前に検証した,

Serverless環境は600倍以上遅い? ~GoとNode.jsとPythonでベンチマークとってみた!~

の通り,そもそもiron-functionsは遅いです.簡単なGoのサーバーレスのサービスでも6,500[ms]程度かかってました.そこから考えると,サムネ生成からレスポンスまで,8,500[ms]-6,500[ms]なので,2,000[ms],約2秒と考えられます.

あなたのサイトは2秒以内?表示速度がフォームコンバージョンに与える影響

によると,

47%のEコマースユーザーは2秒以内にページが読み込まれることを期待している

だそうです.そのため,サムネの表示に2秒程度かかっていては,やはり問題があります.そのため,前段のCDNのようなキャッシュ機構はまだまだ必要なようです.

ただ一方で

今の世の中だと2秒ぐらいでサムネイルを作れる

という見方もあります.
確かに,2秒というレスポンス時間は遅いです.これに実際にサーバーレスの起動の時間が入ると,もっと時間がかかる.Amazon Lambdaが早いといっても,レスポンスに3秒ぐらいかかるかもしれない.ただ

1人サムネイル生成を待てば,あとはCDNがキャッシュしてくれる.

という世界観だとどうでしょう.割と個人的にはありなんじゃないかなーと.1人が3秒ぐらいサムネイルの生成を待つけども,それ以外の人はCDNが高速にレスポンスしてくれる.
 今回,サムネイルサーバーを作ってみて,「結構めんどくさいなぁ.」とか「S3のバケット2つに依存するなぁ」とか,「これサムネイル側のキーの衝突とかちゃんとハンドリングするの面倒だなぁ」とか,「効率化するためにサムネをS3にキャッシュするのだるいなぁ」とか思いました.そういうことを考えると,コードをクリーンに保ちつつ,運用しやすいインフラを考えると,サーバーレス+CDNの構成で十分で,サムネイル自身をバケットに保持してキャッシュする必要はそうそうないんだなーと思いました.
確かに,

サーバレスにサムネイル画像を配信する試み

という記事の中で,

ユーザに投稿してもらった画像は、オリジナル画像としてS3に保存
オリジナル画像は配信しない (private)
サムネイル画像が要求されたらオリジナル画像から動的変換2 し、CDNで配信
サムネイル画像は保存しない
CDN にヒットしなかったらもう一度動的変換

サムネイル画像は保存しない.という方向性があって,へー.と思ってたんですが,これなら割と落としどころとしてはありなラインなんだなぁ.と検証してみて思いました.

感想

最近,後輩を指導してて,

私「この問題は,Aの可能性があります.ただ一方でBの問題も考えられ,Cの問題も考えられます.別の視点だとDの問題も考えられ・・・」
後輩「その原因のうち,どれを選んで,どれから調べればいいんですか?」
私「

という話をしました.
実際のところ,「問題のある箇所を特定するためにテストを行い」「問題を切り分け」「解決する」というのが一般的だと思います.しかし,全てが全てそういう風に出来るわけではないです.
そんな中で「えいや」と問題を割り切って,テストをしたときに,問題の原因が一発で分かる.みたいなところが「勘」と言ってる所以です.

今回,サムネイルサーバーを作ってみて,この「勘」と呼ばれる部分が働かずに苦労しました.私はGo言語に慣れていないのですが,慣れていると「普通は,はまらないところには,はまらない.」ということが出来ます.しかし,慣れていないと,「普通はこういう実装しないよな・・・」というやり方で実装してしまい,「普通は,はまらないところに,はまる」をしてしまい,問題解決にすごく時間を取られて,頭を悩ませたりしました.

あとiron-functions周りもそうでした.これは純粋にナレッジが少ない面もありますが,公式のドキュメントもなくて,厳しかったです.私はあまりDockerやDocker周りのお作法をあまり知らない面もあるので,その面で「勘」が働かず,毎度毎度厳しい感じになってました.

特に,glideによるライブラリのインストールとiron-functionsの連携部分が厳しく,GoとDockerとiron-functionsの3つの領域の重なった領域だったので,かなり時間がかかってしまいました.

そんなわけで日々組んでおかないといざ作るときになると厳しいなぁ.と思うことはよくあります.やはり日々精進が必要なのだなぁと感じた次第でした.

続きを読む