serverless framework のデプロイは遅くないんだよ

これまで

serverless deploy

を叩き続けてました。
叩いては遅いなー、こんなんじゃ開発にならないでしょとか思ってました。
毎回cloudformation動くから遅いんじゃないのとか、分かった風な口叩いてました。
apexの方がデプロイ速いから、apex使ってました。
でも勘違いでした。

serverless deploy function -f YOUR_FUNCTION

で、指定したfunctionだけを爆速でデプロイできました。
調子こいてました。
わかってませんでした。
ごめんなさい。
serverless framework良いっすね。
ありがたく使わせていただきます。

Read More

AWS Step FunctionsでLambda Functionでのサムネイル画像作成をシンプルに

この記事はWanoグループ Advent Calendar 2016の23日目の記事です。
Wanoグループ内でのLambda推しエンジニアとして、前回のLambda@Edgeに引き続き、2016年Re:Inventで発表された、AWS Step Functionsについての書いてみたいと思います。

AWS Step Functionsで解決できること

なんと言っても以下の2点だと思います。

  • Lambda Functionを小さい単位で維持できる
  • Lambda Function間の連携にSQSやDynamoDB等を使用する必要がなくなる

Wanoでも複数のLambda Functionを活用していますが、1つのFunctionの中でいろいろなことをやりすぎたため、他のサービスにそのまま転用できなかったり、コードの見通しが悪くなったりと言ったことが出てきました。
1つ1つのFunctionを細かく分けて、SQS等を使用して連携されることも検討しましたが、リトライや分岐のためにFunctionを用意しないといけないといったことを考えると、腰が重くなっていました。
その問題点を解決してくれるのがAWS Step Functionsです。

Lambda Functionの事例で見かけるサムネイル画像作成をStep Functionsで

Lambda Functionsの事例として、サムネイル画像を作成するサンプルをよく見かけます。
この記事でもサムネイル画像作成を例にし、以下のサムネイル画像あるあるを解決する処理をLambda FunctionとStep Functionsで実現したいと思います。

サムネイル画像あるあるとは

  1. せっかくサムネイル画像で小さくしたのに、Google PageSpeedで調べたら、ロスレス圧縮したら46%小さくなるよと言われる
  2. 適切なContent-Typeヘッダーが付いておらずブラウザで想定外の挙動をする
  3. Cache-Controllヘッダーが付いておらずブラウザキャッシュが効かない

勝手にあるあると言っているだけで、私が良く忘れるだけという可能性が大きいです。。。が、今回はこれを解決することを前提として、進めたいと思います。

Lambda Functionを実装する

Lambda Functionを小さい単位で維持することが重要なので、以下の機能で分けてLambda Functionを実装したいと思います。

  1. ResizeImage: 画像をリサイズするFunction
  2. MinifyJpeg: JPEG画像をロスレス圧縮するFunction
  3. MinifyPng: PNG画像をロスレス圧縮するFunction
  4. SetMeta: メタデータを設定するFunction

実装できたら、これらのLambda Functionを予めデプロイしておきます。
ちなみに私は以前apexを使っていましたが、最近serverless frameworkに乗り換えを進めています。
その内この辺りも記事にできたらなと。

AWS Step Functionsを設定する

State Machineを作る

Step Functionsの設定画面でState Machineを作ります。
設定ファイルは以下です。

注意
2016年12月23日時点で、State Machineの修正はできません。State Machineの設定ファイルもバージョン管理対象としておくことを強くおすすめします(泣きを見ました。。。)
{
  "Comment": "An example of the Amazon States Language using a choice state.",
  "StartAt": "ResizeImage",
  "States": {
    "ResizeImage": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Next": "JpegOrPng"
    },
    "JpegOrPng": {
      "Type" : "Choice",
      "Choices": [
        {
          "Variable": "$.object.fileType",
          "StringEquals": "jpeg",
          "Next": "MinifyJpeg"
        },
        {
          "Variable": "$.object.fileType",
          "StringEquals": "png",
          "Next": "MinifyPng"
        }
      ],
      "Default": "UnsupportedImage"
    },
    "MinifyJpeg": {
      "Type" : "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:MinifyJpeg",
      "Next": "SetMeta"
    },
    "MinifyPng": {
      "Type" : "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:MinifyPng",
      "Next": "SetMeta"
    },
    "UnsupportedImage": {
      "Type": "Fail",
      "Cause": "No Matches!"
    },
    "SetMeta": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:SetMeta",
      "End": true
    }
  }
}

上記ファイルでの設定結果として以下のState Machineができます。

State Machineを実行する

State Machineができたら、早速実行してみましょう。
Lambda Function同様コンソールからJSONを指定して実行できますので、そちらで実行します。

動きました!!
今回は、JPEG画像をイベントとして渡したので、MinifyJpegに遷移しています。
PNG画像を渡せばMinifyPngに遷移する。。。はず(もう23日が終わりそうなので、試す時間がなかったり。。。)

State Machine実行時の注意点

注意点としては、Lambda Functionの実行ではなく、State Machineの実行という点です。
今回StartAtに指定したResizeImageが起動するイベントを実行してもState Machineは実行されず、State Machineの起動が必要になります。
残念ながら、現時点ではState Machineをイベントドリブンにすることはできないようです。
実運用では監視したいイベントを割り当てたLambda Functionを用意して、そちらからState Machineを実行するような構成にする必要があります。

まとめ

いくつかの課題はあるものの、AWS Step Functionsを使うことで、Lambda Function間の連携がシンプルになることは間違いありません。
課題自体も恐らく時間の問題で改善されると思いますし、serverless frameworkといった周辺ツールも続々対応してくると思うので、今後も注目です!!

Read More

Lambda@Edgeを使って色々と試してみようとした

この記事はWanoグループ Advent Calendar 2016の18日目の記事です。

はじめに

Wanoでは、webサーバーの前にCloudFrontを配置し、全てのリクエストを一旦CloudFrontで受けて極力キャッシュを返す形を取ることが良くあります。
この構成のメリットは、サーバーサイドでキャッシュの仕組みを構築しなくても、CloudFrontを前に配置するだけで、EC2にリクエストを流すことなくパフォーマンスを改善できることにあります。

ただ、この構成には弱点も当然あり、ログイン機能が無いサービスの運用には最適なのですが、ログイン機能があるサービスの場合、ログイン状態によってキャッシュを返すかどうかの判定ができないため、ユーザーの9割がログインせずに使用するサービスでも、この構成が一切使えないといった弱点があります。

そんな中、先日のRe:Inventで発表されたのが、このLambda@Edgeです。
本当はLambda@Edgeを実際に使ってみていろいろ試してみようと思っていたんですが、プレビューへの申込みが遅かったためかまだ実際に動かすことができなかったので、ひとまず現時点で試したこと、わかったことを書いておきたいと思います。

Lambda@Edgeの設定

実際にCloudFrontに設定して動かすことはできなかったのですが、辛うじてLambdaのテスト機能で動かすことはできたので、blueprintのコードをベースに、少しだけ試してみました。
まずは設定画面です。

Lambda@Edgeは、CloudFrontのDistributionに対して設定するので、Distribution IDを指定する画面になっています。
その他にも、BehaviorとEventが指定できました。Behaviorが指定できるということは、LambdaFunctionを動かすURLと動かさないURLを分けることができるということなので、静的コンテンツ用にはBehaviorを分けて、LambdaFunctionを実行させずに、パフォーマンスとコスト面への考慮ができそうです。

Eventですが、以下の4つを指定できました。

今回は、この4つのEventからViewer Requestを指定してLambdaのblueprintの中から、cloudfront-ab-testを選択して設定します。

Viewer Request

ブラウザ等のクライアントからCloudFrontがリクエストを受け付けた時に発火するイベントです。
個々のリクエストに対して何か処理を追加する場面で使用するイベントになるため、4種類のイベントの中で最も使う頻度が多いのではないでしょうか。

cloudfront-ab-testを読んでみて

以下のコードが使い方を端的に表しているんじゃないでしょうか。
まだ動かせていないので、実際の挙動はわからないのですが、CookieやHeaderを見て条件に応じてURLをRewriteする。というパターンは使い勝手がありそうな気がします。

let modifiedUri = false;
if (headers.Cookie) {
    for (let i = 0; i < headers.Cookie.length; i++) {
        const experimentIndex = headers.Cookie[i].indexOf(experimentCookieName);
        console.error(experimentIndex);
        if (experimentIndex >= 0) {
            if (headers.Cookie[i][experimentIndex + experimentCookieName.length] === groupA) {
                request.uri = groupAObject;
                modifiedUri = true;
            } else if (headers.Cookie[i][experimentIndex + experimentCookieName.length] === groupB) {
                request.uri = groupBObject;
                modifiedUri = true;
            }
        }
    }
}

書いていて気づいたんですが、Wanoグループ Advent Calendar 2016の前日の記事nginx で特定のIPのみ別の upstream を使うについてもLambda@Edgeで同じようなことができそうですね。

少し前に発表されて、既にTokyoリージョンでも使用できるようになっているALBと組み合わせると、リクエストの捌き方をこれまで以上に柔軟に構成できそうです。

元々やりたかった、ログイン状態に応じてキャッシュを返すかどうかを切り替える場合も、少なくともRewriteすれば対応できそうなので、弊社でも使えるようになったら実際の挙動とパフォーマンスをしっかりチェックしたいと思います。

Read More