AWS Lambdaで遊ぼう(第7話: HTTP API と arm64)

ぉ…

今日も Lambda ネタだ。

はい。 今日の記事は、第6話「Excel ファイルをダウンロード」の続きです。

なのでもし未読の場合は、先に6話目を読んでいただけると有難いです。

tercel-tech.hatenablog.com

あれ1話完結じゃなかったの……。

一応ちゃんと動いたじゃん。

今日は、6話目で作ったマイクロサービスを改良して、HTTP APIarm64対応をやります。

どちらも、処理のパフォーマンスが上がったり課金を節約したり、いいことが多いので、ぜひ!

AWS Lambda 関連のバックナンバーはこちら

■ 今日の目次

前回のおさらい

前回のテーマは、バイナリデータをダウンロードできる Lambda を作ることでした。 Excel ファイルを生成するため、外部ライブラリを利用したりもしましたね。

今日は、そんな前回の成果物を出発点にして、パフォーマンスとコスト効率を一気に改善したいと思います。 どちらもここ2年ほどの間に追加された新機能を使います。

まずは復習がてら、前回のコードを再掲します。

■ template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-excel

  Sample SAM Template for sam-excel

Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      BinaryMediaTypes:
        - '*/*'

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref HelloWorldApi

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

■ hello_world/requirements.txt

openpyxl

■ hello_world/app.py

import base64
from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook

# Excelのファイル名
FILE_NAME = 'sample.xlsx'

def lambda_handler(event, context):
    # Excelブックの新規作成
    wb = Workbook()
    ws = wb.active
    
    # A1セルに数字を書き込む
    ws['A1'] = 42
    
    # Excelファイルのバイナリデータを取得
    excel_data = save_virtual_workbook(wb)
        
    # データをBase64エンコードしてクライアントに返却
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/octet-stream',
            'Content-Disposition': f'attachment; filename={FILE_NAME}'
        },
        'body': base64.b64encode(excel_data).decode('utf-8'),
        'isBase64Encoded': True
    }

REST API から HTTP API

これまで特に触れてきませんでしたが、API Gateway は、AWS SAM 用に REST API (AWS::Serverless::Api)、HTTP API (AWS::Serverless::HttpApi) という2 種類の API タイプが用意されています*1

もともと、API Gateway には REST API しかありませんでしたが、2019年に、その軽量版である HTTP API が登場しました。

現時点で多くの機能をサポートしているのは REST API ですが、新しい HTTP API低レイテンシでコスト効率がよいというメリットがあり、要件に応じて選択すべきでしょう。

docs.aws.amazon.com

コストメリット

ではどれだけのコスト効率かというと、毎月、最初の3億リクエストまでの API コール料金は以下の通りです(2022年2月現在、東京リージョンの場合)。

aws.amazon.com

このように、両者において3倍以上もコストの開きがありますが、sam init で生成される Hello World ボイラープレートのイベントソースには、従来版の REST API が設定されています。

実案件では、さまざまな要件を満たすために REST API を選ぶことも多いものの、今回のサンプル程度であれば HTTP API で充分ですので、こちらに切り替えてしまいましょう。

改修①: REST API → HTTP API

アプリケーションコード (Python) は1行も直さず、template.yaml だけの修正で、REST API から HTTP API に変更できます。

■ template.yaml(HTTP API版)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-excel

  Sample SAM Template for sam-excel

Globals:
  Function:
    Timeout: 3

Resources:
# HelloWorldApi:
#   Type: AWS::Serverless::Api
#   Properties:
#     StageName: Prod
#     BinaryMediaTypes:
#       - '*/*'

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Events:
        HelloWorld:
#         Type: Api       # ★削除
          Type: HttpApi   # ★追加
          Properties:
            Path: /hello
            Method: get
#           RestApiId: !Ref HelloWorldApi

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for $default stage for Hello World function"
#   Value: !Sub "https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/hello"

大きい変更点は、イベントソースの Type を、Api から HttpApi に変えただけ。それだけです。

細かい注意点は、URL の体系が変わっていることです。 今回は簡単のため、HTTP API ならではのデフォルトステージを利用しているため、以前の URL にあった Prod というステージ名が省かれています。

また、URL の末尾に /を付けないよう注意してください(正しい URL を打たないと、{"message":"Not Found"} と表示されてしまいます)。

これで、sam build -usam deploy すると、無事に REST API から HTTP API に置き換わります。

f:id:tercel_s:20220205204333p:plain

arm64 アーキテクチャに対応させる

さて、2021年9月頃、Lambda が arm64 アーキテクチャをサポートしました。

簡単に言うと、Lambda を実行できる CPU の選択肢が増えて、よりパフォーマンスやコストメリットの高い実行環境で Lambda を動かせるようになったということです。

公式の「AWS Lambda デベロッパーガイド」によると、arm64 アーキテクチャを利用する利点について以下のように述べられています。

arm64 アーキテクチャ (AWS Graviton2 プロセッサ) を使用するLambda 関数は、x86_64 アーキテクチャで実行される同等の関数よりも大幅に優れた料金とパフォーマンスを実現します。

(中略)

Graviton2 は vCPU あたりでより大きな L2 キャッシュを提供することにより、メモリの読み取り時間を短縮します。これにより、ウェブおよびモバイルバックエンド、マイクロサービス、データ処理システムのレイテンシーパフォーマンスが向上します。(後略)

夢のようですね。

これは試みないわけにはいきません。

改修②: x86_64 → arm64

なにも難しいことはなく、template.yaml をたった1行書き換えるだけです。

■ template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-excel

  Sample SAM Template for sam-excel

Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
#       - x86_64   # ★削除
        - arm64    # ★追加
      Events:
        HelloWorld:
          Type: HttpApi
          Properties:
            Path: /hello
            Method: get

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for $default stage for Hello World function"
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/hello"
arm64 版 Lambda の注意点

ただし、sam build -u を実行する前に、ちょっとした注意点が2点ほどあります。

■ 注意点1: Amazon Linux2 対応言語か

まず、Lambda ランタイムが Amazon Linux2 に対応している必要があります。 Python の場合、3.8 以降であれば問題ないでしょう。

このほか、Lambda Layers を使う場合は、依存している Layer が arm64 と互換性があるかどうかを充分確認しましょう。

本番環境で既に動いている既存の x86_64 アーキテクチャから arm64 アーキテクチャにいきなり移行するのではなく、公式サイトの「推奨される移行ステップ」に従い、慎重かつ段階的に移行することをおすすめします。

docs.aws.amazon.com

■ 注意点2: sam build前におまじないコマンドを打とう

次に、『AWS Serverless Application Model デベロッパーガイド』にもありますが、AWS Cloud9 で arm64 向けの Lambda をビルドしたり実行したりするには、以下のおまじないコマンドを入力する必要があります。

f:id:tercel_s:20220205211553p:plain:w500

$docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

さもないと、sam build -uコマンドを実行しても「Mounting /home/ec2-user/environment/sam-hello/hello_world as /tmp/samcli/source:ro,delegated inside runtime container」みたいな文字列が表示されたまま、永遠にビルドが進まない地獄に堕ちます。

さて、これで全ての準備が整いました。 ビルド・デプロイしましょう*2

$sam build -u
$sam deploy

f:id:tercel_s:20220205203554p:plain

Outputs セクションで出力した API Gateway のエンドポイント URL をクリックすると、意図したとおり Excel ファイルが落ちてきます。すばらしい。

最後に、Lambda コンソールから、たった今デプロイした Lambda 関数を確認してみましょう。

アーキテクチャが arm64 になっていれば成功です。おめでとうございます

f:id:tercel_s:20220205203823p:plain

まとめ

  • API Gateway には、従来の REST API のほかに、軽量版の HTTP API も用意されている
    • 要件さえ合えば、HTTP API の方がパフォーマンスやコストメリットに優れている
  • Amazon Linux2 で動く Lambda 関数は、arm64 に対応させることができる
    • arm64アーキテクチャは、従来の x86_64 よりもパフォーマンスやコストメリットに優れている
    • Cloud9 で開発している場合、sam build -uでビルドする前におまじないコマンドが必要

*1:厳密には、裏で生成される AWS::ApiGatewayV2::Api リソースは WebSocket もサポートされていますが、こちらは SAM 用の簡易構文がないことと、リアルタイム Web 向けの Pub / Sub 型というやや特殊なプロトコルですので、今回は触れません。

*2:requirements.txt に、aws-xray-sdk が含まれている場合、ビルド時に「Error: PythonPipBuilder:ResolveDependencies - {wrapt==1.13.3(sdist)}」のようなエラーが表示され、ビルドに失敗することがあります。そのような場合は、「$ sam build --use-container --container-env-var WRAPT_EXTENSIONS=false」に変えてビルドを試みてください。

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.