Lambda(C#)の環境変数にアクセスする

今回はLambdaの環境変数C#からアクセスしてみます。 接続するDBを環境毎に切り替える場合などに役立ちます。

AWS Lambda の新機能 – 環境変数とサーバーレスアプリケーションモデル (SAM) | Amazon Web Services ブログ (結構最近追加された機能だったのですね。)

Lambdaの実装

まずはLambdaの実装です。
といってもこれまでのC#プログラミングと同じで、以下のようにEnvironment.GetEnvironmentVariableメソッドでアクセスできます。

// 環境変数"Path"にアクセスする場合
var pathValue = Environment.GetEnvironmentVariable("Path");

今回は"ENV_NAME"という名前で設定された環境変数にコンストラクタからアクセスしています。

using System;
using System.Collections.Generic;
using System.Net;
using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;

[assembly: LambdaSerializerAttribute(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace AWSServerless
{
    public class Functions
    {
        private readonly string envName;
        
        public Functions()
        {
            envName = Environment.GetEnvironmentVariable("ENV_NAME");
        }
        
        public APIGatewayProxyResponse Get(APIGatewayProxyRequest request, ILambdaContext context)
        {
            var response = new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = $"This environment is {envName}",
                Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
            };

            return response;
        }
    }
}

Serverlessでデプロイ

AWSコンソールからでも設定可能ですが、前回同様Serverlessでデプロイしてみます。

ohke.hateblo.jp

serverless.templateのResources.Get.Properties.Environment.Variablesで、環境変数"ENV_NAME"に"test"という値で定義しています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Transform" : "AWS::Serverless-2016-10-31",
  "Description" : "An AWS Serverless Application.",
  "Resources" : {
    "Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless::AWSServerless.Functions::Get",
        "Runtime": "dotnetcore1.0",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [ "AWSLambdaBasicExecutionRole" ],
        "Events": {
          "PutResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        },
        "Environment": {
          "Variables": {
            "ENV_NAME": "test"
          }
        }
      }
    }
  },
  "Outputs" : {
  }
}

これでAWS ExplorerからPublishして、AWSコンソールからデプロイしたLambdaを見ると環境変数が設定されていることを確認できます。 (Serverlessを使わない場合はこの画面から設定します。)

f:id:ohke:20170127223433j:plain

リリースしたURLにアクセスしてみると、"test"を取得できていることも確認できます。

f:id:ohke:20170127223539j:plain

Visual StudioのAWS Serverlessテンプレートを使ってLambdaの環境を作る

12月にAWS LambdaがC#でも使えるようになって、Serverlessでも1.4.0からC#用のテンプレートが提供されましたが、プロジェクトファイルは別で準備しないといけなかったり、ちょっと不便でした。

ところが、AWS SDKでServerless用のプロジェクトテンプレートが提供されていましたので、改めて使ってみました。

プロジェクトの作成

Visual Studioを立ち上げてプロジェクトの新規作成から「AWS Serverless Application(.NET Core)」を作成します(プロジェクト名はAWSServerlessとします)。

f:id:ohke:20170120002943j:plain:w450

肝はserverless.templateで、デプロイするLambdaの設定値やトリガとなるイベントなどを定義します。
サンプルではAPI GatewayでのGetリクエストをトリガとする1個のLambdaが定義されています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Transform" : "AWS::Serverless-2016-10-31",
  "Description" : "An AWS Serverless Application.",

  "Resources" : {

    "Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless::AWSServerless.Functions::Get",
        "Runtime": "dotnetcore1.0",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [ "AWSLambdaBasicExecutionRole" ],
        "Events": {
          "PutResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        }
      }
    }

  },

  "Outputs" : {
  }
}

デプロイ

AWSServerlessプロジェクトを右クリックして、「Publish AWS Lambda...」をクリックするとデプロイ用の画面が開きます。

f:id:ohke:20170120003416j:plain:w450

試しにtest-stackと適当に決めてPublishすると、Visual Studio上でCloudFormationと同じようなウィンドウが表示され、たちまちS3へのアップロード、IAMの設定、LambdaとAPI Gatewayの作成とそれらの紐付けが行われます。

f:id:ohke:20170120003115j:plain

test-stackの作成が完了し、URLにブラウザからアクセスしてみると、Hello AWS Serverlessと表示されます。

AWSコンソールからtest-stackを作成できたことを確認できます。

f:id:ohke:20170120003200j:plain:w450

まとめ

サンプルそのままとは言え、Visual Studio内で完結してノーエラーで環境構築ができたので、個人的にはかなり驚きました。
CloudWatch、DynamoDB、Kinesisともそのうち連携させてみたいと思います。

AWS Lambda(C#)の初回実行が遅いようなので調べました

最近の投稿でC#で書いたプログラムをLambdaで実行させていますが、初回実行が2回目以降と比較して明らかに遅いようなので簡単に調査しました。

構成

PCからJMeterAPI Gatewayへリクエストし、API GatewayがLambdaをキックさせます。

  • Lambda自体は定数文字列をJSON形式で返す以外に何もさせないが、ログストリームからLambdaの実行時間(Duration)をサンプリング
    • Cloudwatchのログストリームの取得では下記投稿のプログラムを使わせていただきました

qiita.com

using Amazon.Lambda.Core;

namespace AWSLambda
{
    public class Function
    {
        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string FunctionHandler(ILambdaContext context)
        {
            return "Hello, World!";
        }
    }
}
  • JMeterからは10スレッドから各10回のリクエスト(合計100リクエスト)を送信して遅延時間(elapsed)をサンプリング
    • Ramp-Upが0秒(全スレッドが同時起動)のため、各スレッドの最初のリクエストでLambda10個が同時に実行開始される
  • PCからAPI GatewayまでのRTTは100ms程度

結果

Lambdaの割当メモリを128MB、256MB、512MBの3パターンで計測してみた結果が、以下の表です。

128MB 256MB 512MB
1回目のアクセス時の平均遅延時間(ms) 5864 4312 2706
2回目以降のアクセス時の平均遅延時間(ms) 1631 413 431
1回目の平均Lambda実行時間(ms) 1869 986 467
2回目以降の平均Lambda実行時間(ms) 110 1.45 0.92
  • 1回目のアクセスが2回目以降のアクセスよりも明らかに時間を要し、メモリ512MBで平均2.7秒
    • 単純な処理時間の増大に加えて、滞留するリクエストの増大によるLambdaの同時実行数の増加も考慮する必要があります
      • 例えば100リクエスト/秒の場合、0.0秒~1.0秒の100リクエストで100個のLambdaが初回実行します
      • 初回実行のため2.7秒を要すると、1.0秒~2.7秒の間に受けたリクエストはLambdaの同時実行数の上限(通常100)に達して500エラーが返されます
  • 初回実行の場合も割当メモリを増やすことで実行時間は改善されるようです
    • 特に実行時間はメモリ割当に対してほぼ反比例しています
    • AWS公式でもメモリと比例したコンピューティングリソースの割当を明言されています

      AWS Lambda のリソースモデルでは、お客様が関数に必要なメモリ量を指定するとそれに比例した CPU パワーとその他のリソースが割り当てられます。
      たとえば、256 MB のメモリを指定すると約 2 倍の CPU パワーが Lambda 関数に割り当てられます。
      128 MB のメモリを指定した場合と比較すると CPU パワーは倍となり、512 MB のメモリを指定した場合と比較すると半分になります。

まとめ

Lambda単体での遅延時間・処理時間を計測することで、2回目以降と比較して初回実行に時間を要することを確認しました。
今回はプログラムをデプロイし直すことで初回実行を再現させていましたが、ある程度時間が経ってから実行すると初回実行と同等の時間を要するようになりますので、設計時に頭に入れておいたほうが良さそうです。