AWS Lambdaまとめ

AWS Lambda についてまとめる。

サーバーレス概要

差別化につながるビジネス実装に集中したい、つまり、以下のような 差別化に繋がらない重い仕事 を避けたい。

  • サーバーのセットアップ: OS・ネットワーク・ミドルウェア・ランタイムなどのセットアップ
  • キャパシティやスケーラビリティの管理
  • 耐障害性を確保するための冗長化
  • セキュリティパッチの適用
  • 差別化に繋がらない機能の実装:スロットリング、認証認可など

上記の労力は「物理マシン > 仮想マシン > コンテナ > サーバーレス」となる。
サーバーレス には以下の特徴がある。

  • サーバーの管理が不要
    • インフラのプロビジョニング・管理・保守
  • 柔軟かつ自動スケーリング
  • 高可用性の自動化
    • 可用性と耐障害性

AWS では Lambda 以外にも以下のようなサーバーレスサービスがある。

  • AWS Step Functions
  • AWS CodePipeline / AWS CodeBuild
  • AWS Serverless Application Repository
  • AWS IAM / Amazon Cognito / Amazon VPC
  • Lambda@Edge / AWS Greengrass
  • AWS Fargate
  • Amazon S3 / Amazon EFS
  • Amazon DynamoDB / Amazon Aurora Serverless
  • Amazon API Gateway
  • Amazon SNS / Amazon SQS / AWS AppSync
  • Amazon Kinesis / Amazon Athena

AWS Lambda は イベントソース から Lambda 関数(ファンクション)が呼び出され ダウンストリーム (サービス)へアクセスする構成となる。(イベントソース -> Lambda 関数 -> ダウンストリーム)

AWS Lambda の基本

Lambda 関数(ファンクション)

  • 標準もしくは カスタムラインタイム でサポートされている言語で記述
  • それぞれが隔離されたコンテナ内で実行され、 1 コンテナ= 1 イベントを処理する
  • 実行時は言語の関数もしくはハンドラーを指定し、 JSON 形式のイベントデータを渡すことが可能
    • イベントソースが S3 の場合、バケット名などがイベントデータとなる
  • コードは依存ライブラリをパッケージングした上でアップロードする( デプロイパッケージ
    • 内部的には S3に保存されており、S3 の ARN で指定することも可能
  • 以下の基本設定が可能
    • メモリ:64MBごとに128MB〜3008MBで指定可能で、CPU能力もこれに比例する
    • タイムアウト:Lambda関数の実行時間(最大 900 秒)
    • 実行ロール:Lambda関数からアクセス可能な AWS リソースをコントロールするための IAM ロール
  • 制限 あり
  • OS のネイティブライブラリを利用する場合は、AWS Lambdaの実行環境(Amazon Linux)を要確認
  • 料金

イベントソース

  • イベントの発生元となるAWSサービスもしくはユーザが開発したアプリケーション
  • 以下の 3 タイプある
    • ポーリング・ストリームタイプ:Lambda関数自体がイベントを検知
      • Kinesis、DynamoDBなど
    • ポーリングタイプ:Lambda関数自体がイベントを検知
      • SQSなど
    • プッシュタイプ:Lambda関数にイベントを通知
  • 以下の分類がある
    • タイプ:
      • 同期:リクエストの処理結果含め返却
      • 非同期:リクエストが正常に受信できたかのみ返却
    • ベース:
      • ポーリングベースかつストリームベース:Lambda関数自体がイベントを検知
      • ポーリングベースかつストリームベースでない:Lambda関数自体がイベントを検知
      • それ以外(プッシュベース?):Lambda関数にイベントを通知
    • 対応サービス
      • 同期・ポーリングベースかつストリームベース:
        • Amazon DynamoDB、Amazon Kinesis Data Streams
      • 同期・ポーリングベースかつストリームベースでない:
        • Amazon SQS
      • 同期・プッシュベース:
        • Amazon Cognito、AWS CloudFormation、Amazon Cloud Watch Logs、Amazon Alexa、Amazon Lex、Amazon API Gateway、Amazon CloudFront、Amazon Kinesis Data Firehose
      • 非同期:
        • Amazon S3、Amazon SNS、Amazon SES、Amazon Cloud Watch Events、AWS CodeCommit、AWS Config、AWS IoT ボタン
    • リトライの動き
      • 同期・ポーリングベースかつストリームベース:
        • データの有効期限が切れるまでリトライ
        • 失敗の間は同一イベントソースからの新規イベントはブロックされる
      • 同期・ポーリングベースかつストリームベースでない:
        • (Amazon SQSなので) Visibility Timeout のちに再実行される
        • 新規イベントの実行はブロックされない
      • 同期・プッシュベース:
        • エラーのステータスコード・内容が返却される
        • リトライはイベントソースの設定による
      • 非同期:
        • 2 回まで自動リトライ(遅延あり)され、その後イベントは破棄される
        • Dead Letter Queue(DLQ)を設定することでイベントを Amazon SQS / Amazon SNS へ移動可能

VPC アクセス

  • VPC 内のリソースへインターネットを経由せずアクセス可能
  • Lambda 関数に対して VPC サブネットおよびセキュリティグループを設定する
    • 既存のものから変更も可能
    • 可用性確保のため、各AZ毎のサブネットを指定しておく
  • 内部的には ENI がフェッチされている
    • IAM ロールに AWSLambdaVPCAccessExecutionRole ポリシーをアタッチしておく
    • 初回アクセスつまりコールドスタート時にはレイテンシ(10〜60秒)が発生する
    • ENI は複数の Lambda 関数から共有される
  • 設定したタイミングからインターネットアクセスは不可能
    • パブリック IP がないため
    • NAT の準備が必要
  • VPC の制限により ENI または サブネット IP が確保できない場合は失敗する
    • 非同期呼び出しの場合は CloudWatch Logs に記録されない

実行ロール

  • IAM ロールで指定する
  • 最低でも CloudWatch Logs へのアクセス許可が必要

同時実行数

  • デフォルトは 1000 で、超えるとスロットリングエラーとなる
  • ConcurrentExecutions と UnreservedConcurrentExecutions メトリクスで確認

ライフサイクル

  1. ( ENI の作成)
  2. コンテナの作成
  3. デプロイパッケージのロード
  4. デプロイパッケージの展開
  5. ランタイム起動・初期化
  6. 関数の実行
  7. コンテナの破棄

利用できるコンテナがない場合はコールドスタートし、再利用できるコンテナがある場合ウォームスタートする。

AWS Lambda の使い方

プログラミングモデル

  • ハンドラー
    • 言語の関数もしくはメソッドで、実行時のエントリーポイントとなる
    • 1 つ目のパラメータとして JSON 形式のイベントデータが渡される
  • コンテキスト
    • ラインタイムに関する情報が含まれる
    • 2 つ目のパラメータとして渡される
  • ロギング
    • 標準出力が CloudWatch Logs に書き込まれる
    • CloudWatch Logs の制限を受けるため、スロットリングによって失われる可能性あり
  • 例外処理
    • 言語仕様によりことなる
    • 同期呼び出しの場合はレスポンスされる
  • 注意点
    • ステートレスにする
    • 同じインスタンスで実行されるとは限らない
      • ローカルリソースの利用・アクセスは処理実行中のみ有効
    • データは他の永続化サービスへ保存する

Lambda 関数の設定

  • VPC アクセスの設定
  • 同時実行数の管理
    • アカンウト毎に割り当てられたデフォルト同時実行数 1000 を関数毎に割り当て可能
    • ダウンストリーム( DB とか)に対する流量制御に利用
  • 環境変数
    • キーと値のペアで設定可能
      • キーと値の合計が 4kB 以下、英字開始で英数字とアンスコのみ
    • デフォルトの環境変数もあり、関数の中から参照可能( AWS_REGION など)
      • リザーブドがありの場合は上書き不可能
    • 関数のバージョニング機能に環境変数も保存される
    • AWS Key Management Service によりデプロイ後に自動的に暗号化
      • デフォルトでキーが自動生成されるが、独自キーの適用も可能
  • バージョニング
    • Lambda 関数と環境変数などの情報を PublishVersion に保存可能
    • バージョンに対してエイリアス設定・付け替え可能
    • routing-config を利用することでエイリアスに対してトラフィックの割合を管理可能(カナリア的な)
  • Lambda Layers
    • 各 Lambda 関数から共通で参照可能なコンポーネントを定義・参照可能
    • ZIP でアップロード
    • カスタムランタイムを Layer としてアップロードも可能
    • /opt の下に指定した順に展開される(同名の場合後勝ち)
    • 非圧縮で 250MB まで( Lambda 関数と同じ)
  • タグ付け
    • 他の EC2 などのサービスと同様でタグ付け可能で、グループ化とフィルタリングに利用
  • デッドレターキュー( DLQ
    • 非同期実行時に 2 回リトライ行われた後にも失敗した場合にイベントを退避可能
      • Amazon SQS/SNS
    • 退避先へアクセスする権限付与が必要
    • DLQ 自体に書き込めない場合 DeadLetterErrors メトリクスに記録されイベントは破棄される
  • Custom Runtimes
    • Linux 互換のランタイムを持ち込み可能 -> 任意の言語を実行可能になる
    • Custom Runtimes を利用する場合は Lambda 関数に bootstrap と呼ばれる実行ファイルを含める必要あり
      • Lambda Layers として登録も可能
    • bootstrap に以下の 2 つを実装する必要あり
      • Initialization Tasks :ループしてイベントを待ち受ける
        • 環境変数の読み取り、関数の初期化、エラーハンドリングなどを実装する
      • Processing Tasks :請求時間とタイムアウトの対象
        • イベントの取得、トレーシングヘッダの伝播、コンテキストオブジェクトの作成、関数ハンドラ呼び出し、レスポンス・エラーのハンドリング、クリーンアップなどを実装
  • モニタリング
    • CloudWatch を利用したメトリクス監視
    • X-Ray を利用したトラブルシューティング
  • 注意点
    • Labmda 関数が保証するのは最低 1 回実行されること
      • 冪等性を確保するなど設計しておく必要あり

アプリケーションの管理

  • AWS Serverless Application Repository
    • Lambda アプリケーションのリポジトリ
  • AWS Serverless Application Model ( SAM )
    • サーバーレスアプリケーションの定義とパッケージング・デプロイ
    • サーバーレスに最適化した Cloud Formation
    • インテグレーションテストでも活用
    • ローカル開発では AWS SAM CLI を利用
  • Github -> CodeBuild -> SAM -> 3rd Party Tools でテスト -> プロダクションリリース(SAM)
    • 全体を CodePipeline で

ペストプラクティス / アンチパターン

Lambda を速くするポイント

  • いかに関数を効率よく実行するか
  • コールドスタートに関わる部分を速くする
  • 関数コードを速くする

コールドスタート

Lambda の実行は以下を経て行われる。

  1. 関数コードのダウンロード
  2. 新しいコンテナの開始
  3. runtime の起動
  4. 関数コードの実行

1 〜 3 をコールドスタート、 4 をウォームスタートと呼ぶ。
1 と 2 は AWS が最適化する部分、 3 と 4 はユーザが最適化する部分となる。
1 と 2 はメトリクスでは確認できないが、一部は X-Ray ( Initiallization および差分 )で確認可能。

コールドスタートを速くする

  • コンピューティングリソースを増やす
    • メモリ設定 -> メモリに比例して CPU 能力も上がる
    • コストを気にしてメモリを小さくしても、実行時間が短くなればコストは下がる -> コストはあまり変わらない可能性
  • ランタイムを変える
    • 例えば JVM の起動は遅い -> 一度起動すると速いが
  • パッケージサイズを小さくする(効果小)
    • ダウンロードを速く、依存関係を減らしロードを速く
    • ProGuard(Java)、UglifyJS(Node.js) のようなコード最適化ツールを使う
  • VPC は必要でない限り利用しない(効果大)

効率的な関数コード

  • コードの最適化
    • 各言語のベストプラクティス
  • Fat でモノリシックな関数にしない
    • コード内ではオーケストレーションしない -> Lambda から Lambda を実行しない -> Step Function とか使う
  • ハンドラとコアロジックは分離させる
    • コアロジック(=ビジネスロジック)の単体テストが容易になる
  • コンテナの再利用を有効化する
    • グローバルスコープを賢く活用する
      • AWS SDK や DB クライアントの初期化はハンドラの外側で行う
      • グローバルスコープはコールドスタートでしか実行されない
  • 必要なデータのみロードする
    • DB アクセスの select 最適化など
  • レジリエンスの向上
    • エラーハンドリング -> タイムアウトを適切に管理し、リトライ処理を実装する
    • Lambda のリトライポリシーを理解する -> 同期はリトライなし、非同期は 2 回リトライ、ストリームは期限が切れるまで
    • Dead Letter Queue を活用 -> 関数毎にできれば Queue も分けて設定する
  • 複雑な依存関係を避ける
  • 組み込まれた SDK は使用しない
    • Lambda の実行環境にはランタイムによって AWS SDK などが組み込み済み
    • ラインタイムは更新されるので、ユーザで SDK をパッケージングする
  • 非同期実行を活用する
    • 同期実行は同時実行数の制限に引っかかりやすく、スケーラビリティの観点では非同期がおすすめ
    • 非同期はバーストも若干許容される
  • 冪等性を確保する
    • Lambda で保証するのは「最低 1 回実行すること」、「 1 回だけ実行すること」ではない
    • DynamoDB を利用するなどして冪等性を担保する -> トリガーされた時のイベント ID を管理
    • S3 がイベントトリガーの場合は、処理のインプットとアウトプットのバケットを分ける
  • Think Parallel
    • Lambda の中でループするのではなく、ループ回数分 Lambda を並列に起動・実行するなど
  • Java の場合
    • POJO ではなくバイトストリームを使う
  • ハードコーディングしない
    • コード、設定、データを分離する
    • 環境変数を利用 -> 特に認証情報は
    • AWS Systems Manager Parameter Store や AWS Secrets Manager の利用も検討
  • AWS Systems Manager Parameter Store
    • 階層型で、パスワード、データベース文字列などのデータをパラメータ値として保存
    • 値は暗号化可能
    • Amazon SNS を用いた変更通知可能
    • AWS KMS、CloudWatch、CloudTrail と統合可能
    • 追加料金不要
    • ssm_cache ライブラリの利用を検討
  • AWS Secrets Manager
    • DB 認証情報、パスワードなどの秘密情報を一元管理可能
    • ライフサイクルを通じて容易にローテーション、管理、取得可能
    • RDS、 RedShift、DocumentDBとの統合が組み込み済み
    • 有料、、、
  • ストリーム型
    • Kinesis Data Streams、DynamoDB Streams
    • バッチサイズは Lambda 関数の実行あたりの最大レコード数
      • バッチサイズが大きいと同時実行数は低下する
      • バッチサイズに関わらず、 1 バッチで最大 6 MB までしか処理できない
    • 処理失敗するとブロックされるため、 Lambda は成功を返却するようにする
      • ログ出力や DLQ へ流して成功を返すようにする
    • 複数のコンシューマを用意するのは避ける
    • ファンアウトパターン
      • ストリームから読み取る Lambda を 1 つの Dispatcher として、そこから複数の Lambda 呼び出し・並行処理する
    • リトライや順序が重要でない場合は SNS の利用も考える
  • Lambda から利用するデータベース
    • RDBMS はアンチパターン
      • コネクション数の問題:DB側の橙同時接続数を超えやすい
      • VPC コールドスタートの問題:通常のコールドスタートに比べて 10 秒程度の時間を必要とする
      • 実行数が少なく遅延が許容できる場合は問題ない
    • 選択肢
      • DynamoDB:コネクション数の問題がなく、 VPC 外リソースとしてアクセス可能、サービス停止無しでスケールアップ
      • RDBMS の場合は、、、
        • 反映は非同期にする -> ストリーム・キューを介して Lambda でデータ反映
        • 同時実行するを制限する -> RDBMS へアクセスする Lambda を限定してから
      • Amazon Aurora Serverless
        • サーバーレス用の Aurora ではない -> 現状は若干飛び道具?
  • IP 固定はアンチパターン
    • コースドスタート問題、、、
  • IAM ポリシーはきつく絞る
  • Lambda で全部やろうとしない

セキュリティ

AWS Lambda のセキュリティ

AWS Lambda のサービスはコントロールプレーンとデータプレーンに分けられる

  • コントロールプレーン
    • ファンクション管理 API を提供
    • AWS サービスとのインテクレーションを管理
  • データプレーン
    • Invoke API のコントロール
    • Lambda 関数が Invoke されると実行環境を新たに割り当てる、もしくは既存の実行環境を選択する

データプレーンと MicroVM

  • 各関数は 1 個以上の専用実行環境で実行される
    • かくじっこう環境は 1 同時実行を処理するが、同じ関数が複数回連続実行される場合は再利用される
  • 関数の実行環境は MicroVM 上で実行される
    • MicroVM は AWS アカウントごに専有で割り当てられ、同一アカウント内では関数を跨って再利用される
  • MicroVM は AWS が所有し管理するプラットフォーム( Lambda Workers )上に用意される
  • 実行環境は異なる関数間で共有されることはなく、 MicroVM は異なる AWS アカウント間で共有されることはない

実行環境の隔離

各実行環境は以下のコピーを持っている

  • 関数コード
  • Lambda layer
  • 組み込みもしくはカスタムラインタイム
  • Amazon Linux ベースの最小限ユーザーランド

実行環境は Linux カーネルベースのコンテナテクノロジで隔離され、他の環境に属するデータへのアクセスおよび変更は不可。

  • cgroup
    • 実行環境ごとにに CPU 、メモリ、ディスク帯域、ネットワーク帯域へのリソースアクセスを制限
  • namespace
    • プロセス ID 、ユーザ ID 、ネットワーク・インターフェースとその他のリソースをグルーピング
    • 各実行環境は専有の namaspace で実行される
  • seccomp-bpf / セキュアコンピューティングモード
    • 実行環境ないから利用されうる syscall を制限
  • iptables/routing tables
    • 実行環境をお互い隔離
  • chroot
    • ファイルシステムへの特定範囲でのアクセスを提供

ペイロード

Lambda 関数の呼び出しタイプによってペイロード(ユーザから送信されるデータ)は異なる経路で処理される。

  • 同期呼び出し( RequestResponse )
    • API Caller ( API Gatewary や SDK など)からロードバランサに渡され、その後 Lambda invoke service に渡される
    • Lambda invoke service はリクエストされた関数の実行環境を特定し、ペイロードをその実行環境へ渡す
    • インターネットごしのロードバランサへのトラフィックは TLS で保護される
    • Lambda サービスないのトラフィックは単一リージョンにおけるインターナル VPC を通る
  • 非同期呼び出し( Event )
    • Amazon SQS にキューイング
    • 内部的な poller プロセスがメッセージを取得し Lambda invoke service へ渡す
    • SQS のキューは Lambda さーびすによって管理されていてユーザには見えない
    • このパス上のトラフィックは TLS で保護されているが、 SQS でのデータ保管に当たっては追加で何かしらの保護はしていない

Audit

  • Amazon CloudTrail
    • AWS アカウント全体の統制、コンプライアンス、オペレーション監査、リスク監査を実装可能
  • AWS Config
    • Lambda 関数、ランタイム、タグ、ハンドラ名、コードサイズ、メモリ割り当て、タイムアウト設定、同時実行数の設定に対する設定変更や削除を追跡可能

関数コードのセキュリティ

  • AWS アカウントをセキュアにする
    • ルートアカンウトを無効にする
    • 環境ごとに AWS アカウントごと分けてしまうのも有効
  • AWS IAM のロールを最小限の権限で割り当てる
  • 認証情報をハードコーディンスしない
    • 環境変数の利用、 KMS による暗号化、 AWS Systems Manager Parameter Store や AWS Secrets Manager を利用
  • アプリケーション固有のバリデーションは適切な方法で実装する
  • デプロイ前に依存ライブラリの脆弱性スキャンを実施( CI/CD パイプラインへ組み込む)
    • OWASP 、 Snyk 、 Twistlock

ユースケース・事例

  • Amazon API Gateway -> Lambda -> DynamoDB
    • WebSocket への対応も可能
  • AWS AppSync -> Lambda -> DynamoDB
  • S3 -> Lambda -> S3 など
    • MapReduce ライクに並列実行も可能
  • SQS -> Lambda -> …
  • Amazon Kinesis Data Stream -> Lambda -> …
  • Alaxa スキル
  • AWS IoT Corea -> Lambda -> DynamoDB
  • Amazon DynamoDB Streams -> Lambda -> …
  • CloudWatch ジョブ/CRON -> Lambda

上記組み合わせて利用することも可能。