ボクココ

個人開発に関するテックブログ

AWS Lambda を利用する上で知っておいたほうがよいこと

本ブログで度々紹介している AWS Lambda。改めてもう一度解説すると、コード(Node.js or Java)を実行する環境をAWS側で用意してくれる。"実行したいときに用意したコードを実行できる"ため、必要な料金を最低限に抑えられる。私が最も気に入っているAWSサービスの一つだ。

さて、具体的な使い方に関しては AWS Lambdaの公式ドキュメントを始め、Qiitaなどでもやってみた系でたくさんあるので、本エントリーではそれを利用する上で知っておいたほうがよりよいことを挙げる。というより私が実際に利用してみて詰まったところや感動したことなどを挙げてみる。

コード失敗時にリトライをデフォルトで3回行う

公式ドキュメントにもこのことは書いてあるのだが、見落として不可解な気持ちになるので、これは必ず知っておいたほうがよい。Node.js の場合、Lambdaのコード実行完了時にcontext.done() を始め、 context.succeed(), context.fail() を呼ぶことができる。

ここで、丁寧に失敗したときに context.fail()を呼ぶと、それが呼ばれた時点で3回リトライを勝手にAWS Lambdaが実行してくれるのだ。

つまり失敗しても確実に1回だけ実行させたい、という場合は context.done() を使うのが無難だろう。

/tmp フォルダのパーミッションに注意

AWS Lambda では、一時作業ディレクトリとして /tmp フォルダが誰でも読み書きできる領域として提供してくれている。よくあるパターンとしては、S3から画像などを/tmp にダウンロードして、そしてLambdaの環境内で何かしらの操作を行うというパターンである。以下のようなコードを書くであろう。

 var s3 = new aws.S3();
 s3.getObject({ Bucket: "bucket_name", Key:"file_name"}, function(err, res) {
    var paths = meta.extra.filename.split('/');

    // ここ重要!
    var filePath = '/tmp/' + (new Date()).getTime() + paths[paths.length - 1];

    try {
      fs.writeFileSync(filePath, res.Body);
      callback(null, filePath);
    } catch (err) {
      callback(err);
    }

コメントの所で、(new Date()).getTime()としている点に注目してほしい。AWS Lambdaは実行のたびに違うLinux空間を使う、とは限らないようで、同じLinux環境を使い回すときもあるようだ。ただ、毎回実行するユーザーは違うので、同じファイル名を/tmp に書き込もうとすると、パーミッションエラーがたまに発生するという事態が発生する。

毎回実行後にファイルを削除するようにするか、上記のように毎回ファイル名を被らないようにするかのどちらかの対策が必要となる。

にしても /tmp 使えるのは大変ありがたい。

AWS Lambda内で実行できるコマンドを把握する

AWS Lambda 内で利用可能なコマンドは以下参照先の下の方をご覧いただきたい。

AWS Lambdaをいろいろ暴く - Qiita

これらのコマンドを Node.js から実行させることができる。例えば、ファイルの文字コードを変換する iconvを AWS Lambda環境内から実行させたい場合は以下の通りだ。

      var exec = require('child_process').exec;
      var cmd = "iconv -f sjis -t utf-8 " + filePath;
      var child = exec(cmd, function(err, stdout, stderr) {
        if (err) callback(err);
        callback(null, stdout.toString());
      });

上記コードを実行できるので AWS Lambda のできる範囲がかなり広がっていると思う。むしろなんでもできるという表現が正しい気がする。

AWS Lambda はいつでもどこからでも実行可能

初めて AWS Lambda の言葉を聞いたときは、 S3のファイルアップロードのタイミングだとか、 Kinesiss で溜まったデータを引っ張ってくるだとか、DynamoDBにデータ入った場合だとかのように、 AWS サービス内の任意のイベントをトリガとして実行できるもの、というイメージが強かった。

しかし、実際は全くそんなことはなくて、自分から直接 AWS Lambda を任意のタイミングで呼ぶことが可能だ。

それを利用するには 各言語に対応した AWS SDK を利用する。AWS SDKにAWS Lambdaを実行させることのできるコードも含まれている。Rubyの場合はこんな感じだ。

  lambda_client = Aws::Lambda::Client.new
  func_name = "my_lambda_function"
  invoke_params = {
    function_name: func_name,
    invocation_type: "Event",
    log_type: "None",
    client_context: "String",
  }

  str=<<JSON
{"Records": [ { "s3":
        { "bucket":{"name":"mybucket"},
          "object":{"key":"path/to/file/meta.json"} }
} ] }
JSON

  invoke_params[:payload] = str

  resp = lambda_client.invoke(invoke_params)

これは Android でも iOSでも、JavaScriptでも同様のことが可能である。つまり、WebAPIのように振舞うことも可能だということだ。

さらに最近は Amazon API Gateway の登場により、 HTTP のURLを叩くだけで AWS Lambda を実行することもできるようになった。

このことからも AWS Lambda は なんでもできる ということがわかるかと思う。

S3イベントの場合は、 キー名の prefix や suffix でイベント発火を限定できる

S3限定だが個人的に便利だと思ったので紹介する。 S3の何かしらのイベントをトリガとしてLambdaを実行させる場合、基本的にはバケット単位で指定する。ただ、S3内でキー名で一致したものだけ実行というようにフィルタリングをかけることができる。

こうすることで、特定のファイルに限定したり、環境ごとに実行するLambdaファンクションを切り替えたりすることができる。AWS Lambda内でそのような処理を書く必要がなくなるため、S3とLambdaを連携させる場合は必須で設定しておきたい項目だ。

終わりに

AWS Lambdaを利用すれば、 ほぼ無料でコードを実行できる環境を外部に用意できる。これこそ最大のメリットである。AWS Lambdaの可能性を感じていただけたら、ぜひ AWS Lambda にトライすることをお勧めする。

私は何か実装するたびに「AWS Lambdaでできないか」と考えるようにしている。