Tech Blog

グローバルな家族アプリFammを運営するTimers inc (タイマーズ) の公式Tech Blogです。弊社のエンジニアリングを支える記事を随時公開。エンジニア絶賛採用中!→ https://timers-inc.com/engineering

GAEで画像リサイズ処理を書いてみた

サーバーエンジニアの下川です。

先日のre:inventでもあったとおり、最近サーバーレス・アーキテクチャについて耳にする事が多くなってきました。

TimersでもLambdaを使用して写真リサイズ等の処理をサーバレス化し運用しています。 今回は写真のリサイズをGoogle App Engine(以下、GAE)で実装するとどうなるのか?というのを試してみました。

言語はGolangを使用しました。

環境構築

せっかくDockerを学習中なので、Dockerfileで書いてみました。
また、2017年2月時点でのGAEでのgolangのバージョンは1.6.2です。

FROM google/cloud-sdk

RUN rm /bin/sh && ln -s /bin/bash /bin/sh
RUN apt-get update -y && \
  apt-get install -y --no-install-recommends \
  git \
  curl \
  mercurial \
  make \
  binutils \
  bison \
  gcc \
  build-essential \
  netcat \
  unzip

# Install golang
ENV GOLANG_VERSION 1.6.2
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a

RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
    && echo "$GOLANG_DOWNLOAD_SHA256  golang.tar.gz" | sha256sum -c - \
    && tar -C /usr/local -xzf golang.tar.gz \
    && rm golang.tar.gz

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
WORKDIR $GOPATH

COPY go-wrapper /usr/local/bin/

# Google App Engine
RUN wget -O appengine.zip https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_linux_amd64-1.9.40.zip
RUN unzip -q appengine.zip -d /appengine && \ 
  rm -f appengine.zip
ENV PATH /appengine/go_appengine:$PATH

Docker初心者の私ですが、せっかくなので、この度初めてdocker hubに上げてみました。 https://hub.docker.com/r/shimopri/gae_go/

さて、これで環境構築は完了です。 Hello, Worldまではこちらに記載されている通り簡単なので割愛します。

リサイズ処理が簡単すぎる

画像のリサイズ処理は、imageパッケージを使用すると非常に簡単です。 リサイズ元の写真がBlobstoreにある場合、以下のファンクションでリサイズ後の画像URLを取得する事ができます。

func ServingURL(c context.Context, key appengine.BlobKey, opts *ServingURLOptions) (*url.URL, error)

第二引数にはBlobKeyを指定します。 もし、CloudStorageに画像が配置されている場合は以下のファンクションでBlobKeyを取得すればOKです。

func getResizeUrl(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    p := "/gs/hoge/hoge.jpg"
    size := 40
    
    // CloudStorage内のファイルのblobKeyを取得する
    blobKey, err := blobstore.BlobKeyForFile(c, p)

    〜略〜

    // resizeのパラメータを指定しURLを取得
    opts := image.ServingURLOptions{Secure: false, Size: size, Crop: true}
    servingURL, err := image.ServingURL(c, blobKey, &opts)

    〜略〜
}

resize処理自体はLambda(Node)だと imagemagick を使用して実装できますが、Step数はGAEの方が短くなりそうです。

動的にサイズ変更が可能

ServingURLは非常に優秀で、パラメータを変更する事により、動的にリサイズされます。

http://ServingURL=s32 のように、URLに s32 と付与すると、長辺を32ピクセルアスペクト比を保持してリサイズします。 サイズは最大1600ピクセルまで指定可能で、何回か叩きましたが、速度も200msec程度と高速でした。

また s32-c と付与すると、中心から32ピクセルでクロップ&リサイズします。

imagemagick でも同様の事は実装できますが、これが標準で使えるのは便利すぎです。

ローカルでの動作確認

以下のコマンドでローカル環境で動作確認をします。

goapp serve

これで http://localhost:8080 で動作確認ができます。 ちなみに、ServingURLの結果は実際にdeployしないと確認する事ができません。 http://localhost:8080/_ah/img/encoded_gs_file:xxxxxx=>root@xxxxx のようなstringが返却されたら成功です。

デプロイ

以下のコマンドでdeployします。

goapp deploy -application [PROJECT_ID]

初回のdeploy時は以下のようなメッセージが表示されるので、メッセージにしたがって認証画面に移動します。

〜中略〜

Please visit https://developers.google.com/appengine/downloads
for the latest SDK
****************************************************************
12:15 PM Starting update of app: hogehoge, version: 1
12:15 PM Getting current resource limits.
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?scope=hoge(略)

If your browser is on a different machine then exit and re-run this
application with the command-line parameter

  --noauth_local_webserver

今回Dockerコンテナ上で作業しているので少し特殊ですが、手順は以下の通りとなります。

(1) https://accounts.google.com/o/oauth2/auth?scope=hoge(略) をブラウザで叩く (2) Googleアカウントで認証を行う (3) http://localhost:8080/?code=xxx(略) にリダイレクトされる (4) コンテナに戻り curl http://localhost:8080/?code=xxx(略) を叩く。 (5) 認証成功。deployが始まる。

こちらの作業は初回のみです。

deployが成功すると https://[PROJECT_ID].appspot.com/resize にエンドポイントが作成されて、APIの完成です。(今回は resize というエンドポイントを作成しました。)

AWSの場合、Lambdaの前にAPIGatewayを配置して、Method typeごとに詳細な設定をしてエンドポイントを作成しますが、GAEの場合設定はこれだけでエンドポイントが作成されます。

ログ

GAEはエンドポイント作成の容易さもさる事ながら、ログの見易さがLambdaのそれを凌駕しています。 標準で以下のようにかなり見やすいログ機能が備わっています。 フィルタも早いし便利すぎる。

f:id:timers-tech:20170213001220p:plain

一番下は敢えてエラーを発生させてみたのですが、以下の通りきちんとログに書き込まれました。

f:id:timers-tech:20170213001234p:plain

AWSのAPIGateway + Lambdaの場合、CloudWatchLogsにログは書き込まれますが、ここまでの見易さはありません・・・ TimersではアプリケーションログをLambda内でファイルに書き出してS3に送信していますが、GAEのこのログ機能があれば自前でそういったログ機能を作成する必要はなさそうです。

まとめ

今回は、Dockerの勉強かねて環境構築しましたが、単純にローカル環境にGAEのSDKをインストールして、deployするだけであればすぐ開発に取り掛かる事ができます。

LambdaではAWSのコンソールのみで完結する事ができるので、一見入り易いように見えますが、開発して、バージョン管理して、ロール管理して、テストして等の通常の開発のライフサイクルを回すとaws-cliを使用したり、Chalice等のフレームワークを使用していく事になり、結局はcliを使う事になると思っています。

GAEでは最初からGAEのSDKとコンソールで全てが完結するように設計されており、スマートな印象を受けました。

今回はGAEの記事なので、APIGateway + Lambdaの良いところには触れていませんが、APIGateway + Lambdaの方の利点もあります。

TimersではAWSを中心にインフラ設計されているので、一部をGAEでマイクロサービス化するのは難しさも感じますが、APIGateway+Lambda、GAE、それぞれの利点を見極めて、より良い方を選択して行きたいと思います。

個人的にはどこかでGAE使いたい!と思っていますので、チャンスを見つけて提案して行きたいです!

積極採用中!!

子育て家族アプリFamm、カップル専用アプリPairyを運営するTimers inc. では、現在エンジニアを積極採用中! 急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください! 採用HP : http://timers-inc.com/engineerings

Timersでは各職種を積極採用中!

急成長スタートアップで、最高のものづくりをしよう。

募集の詳細をみる