AWS SAMのGo言語Lambdaアプリケーションのランタイムを更新してみた

2023.09.11

しばたです。

先月よりAWSから「AWS LambdaのGo 1.xランタイムのサポートが2023年12月31日で終了する」旨の通知が案内されており、既に多くの方がランタイムの移行を試されているかと思います。

この通知に対してしばらく他人事でいたのですが、以前Go言語で簡単なAWS SAMアプリケーションを作っていたことを思い出したため、改めて現状を整理しランタイムの移行を試みました。

Go言語のLambdaランタイムおさらい

Go言語Lambdaの概要と移行手順についてはAWS Compute Blogの以下の記事が分かりやすいです。

Go言語のLambdaは最初にAWSが提供するAmazon Linux 1ベースのgo1.xが提供されており、多くの方はこのランタイムを使っていると思います。
Amazon Linux 1ベースのため2023年12月31日でサポートが切れ非推奨化フェーズ 1になります。

その後Lambdaがカスタムランタイムをサポートする様になり、Amazon Linux 1ベースのprovidedおよびAmazon Linux 2ベースのprovided.al2上でGo言語アプリケーションを動作させるパターンが増え、さらにLambdaがコンテナイメージをサポートしたことで任意のイメージ上でGo言語アプリケーションを動作させることも可能になりました。 

現時点においてgo1.xの後継となるランタイムは発表されなかったため、go1.xの利用者はカスタムランタイムprovided.al2か独自のコンテナイメージを使う様にアプリケーションを移行する必要があります。

aws-lambda-go モジュール

Go言語でLambda関数を実装する場合aws-lambda-goを使用します。

このモジュールがLambda内部のAPI(Runtime API)をよしなに扱うことで開発者はアプリケーションの実装に注力できる形になっています。

前述のAWS Compute Blogを読むまで知らなかったのですが、go1.xランタイムではランタイム内部にRuntime APIを扱う層(Runtime Client)が用意されておりaws-lambda-goはこのRuntime ClientとRPC通信をする実装になっています。

(Migrating AWS Lambda functions from the Go1.x runtime to the custom runtime on Amazon Linux 2より引用)

対してカスタムランタイムの場合は開発者が直接Runtime APIを扱う必要があり、aws-lambda-goにおいてはVer.1.18からカスタムランタイムに対応しています。

(Migrating AWS Lambda functions from the Go1.x runtime to the custom runtime on Amazon Linux 2より引用)

内部実装としてはRPCを使うパターン、Runtime APIを直接実行するパターンの二種類の実装を実行環境に応じて使い分ける様になっていました。
_LAMBDA_SERVER_PORT環境変数が定義されている場合はRPCを使い、AWS_LAMBDA_RUNTIME_API環境変数が定義されている場合はRuntime APIを直接利用します。

また、Goアプリケーションのビルド時にlambda.norpcタグを付けてやることでRPCに依存する処理を除外しバイナリサイズを減らすことも可能です。

# lambda.norpc タグを付けたビルド例
GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o bootstrap main.go

ざっくりまとめ

ここまでの内容をざっくりまとめると下表のとおりとなります。

ランタイム ベースOS aws-lambda-go 内部通信 サポート期限
go1.x Amazon Linux 1 任意のバージョン RPC→Runtime API 2023年12月31日
カスタムランタイム (provided) Amazon Linux 1 Ver.1.18以降 Runtime API 2023年12月31日
カスタムランタイム (provided.al2) Amazon Linux 2 Ver.1.18以降 Runtime API 未定
コンテナイメージ イメージ次第 Ver.1.18以降 Runtime API イメージ次第

provided.al2のサポート期限は現時点で決まっていませんが、Amazon Linux 2のサポート期限が2025年6月30日までなので概ね同時期にLambdaのサポートも切れることでしょう。

AWS SAMの場合

AWS SAMではVer.0.26からGoランタイムgo1.xのビルドをサポートしており、AWS::Serverless::FunctionリソースのRuntimeプロパティを指定してやればよしなにビルドしてくれます。

template.yaml

# AWS SAMでの記述例
Resources:
  # Lambda function
  OjichatFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: cloudwatch-ojichat-widget
      CodeUri: function/
      Handler: main
      Runtime: go1.x

具体的なビルド処理はAWS Lambda Buildersに記述されています。
Go言語の場合はざっくり以下の様なパラメーター指定でビルドする様になっています。

export GOOS=linux 
export GOARCH=Architecturesで指定されたアーキテクチャ
# 条件により -trimpath パラメーターが付くこともある
# デバッグ時は -gcflags all=-N -l パラメーターが付く
go build -o "Handlerパラメーターで指定された値" "CodeUriで指定されたパス"

残念ながらlambda.norpcタグには対応してない様でした。

そしてgo1.xランタイムをprovided.al2に変える場合はこんな感じにしてやります。

template.yaml

# provided.al2 で Go言語Lambdaをビルドする例
Resources:
  # Lambda function
  OjichatFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: cloudwatch-ojichat-widget
      CodeUri: function/
      Handler: bootstrap
      Runtime: provided.al2
    Metadata:
      BuildMethod: go1.x

AWS Lambda Builders Ver.1.21.0 (AWS SAM Ver.1.62.0)よりメタデータのBuildMethodでgo1.xの指定が可能となっており、カスタムランタイムを使いつつ前述のオプションでGoアプリケーションのビルドが可能になっています。

加えてカスタムランタイムではバイナリファイルの名前をbootstrapにする必要があるため、Handlerプロパティの値をbootstrapにしてやります。

これだけでOKです。

移行してみた

ここまでの内容をふまえて実際に移行したアプリケーションを紹介します。

CloudWatch custom widget用に非常にシンプルなGo言語のLambda関数を実装しています。
今回のために予め開発環境やモジュールバージョンを刷新し、

  • Go 1.21.1
  • AWS SAM Ver.1.97.0
  • aws-lambda-go v1.41.0

で動作する様にしています。
移行前の状態はこんな感じでgo1.xランタイムで動作していました。

template.yaml

# 一部抜粋
Resources:
  # Lambda function
  OjichatFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: cloudwatch-ojichat-widget
      CodeUri: function/
      Handler: main
      Runtime: go1.x
      Tracing: PassThrough

前節の内容に倣いランタイムをprovided.al2に更新し、ついでにアーキテクチャも単価の安いarm64に変更しておきました。

template.yaml

# 一部抜粋
Resources:
  # Lambda function
  OjichatFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: cloudwatch-ojichat-widget
      CodeUri: function/
      Handler: bootstrap
      Runtime: provided.al2
      Architectures: [arm64]
      Tracing: PassThrough
    Metadata:
      BuildMethod: go1.x

この状態でsam buildおよびsam deployを実行し再デプロイしてやれば移行は完了です。

シンプルなアプリケーションなのでこれだけで特に問題無く動作しています。

実運用されているアプリケーションの場合はもう少し事前検証が必要になると思いますが、手順としては大差ないでしょう。

補足 : Makefileビルドする場合

AWS SAMのバージョンが古かったりGoのビルド時にlambda.norpcタグを付けたい場合はMakefileを使ったビルドをしないといけない様です。

こちらの記事を参考にするとざっくり以下の様な記述で動作するはずです。

template.yaml

Resources:
  # Lambda function
  OjichatFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: cloudwatch-ojichat-widget
      CodeUri: function/
      Handler: bootstrap
      Runtime: provided.al2
      Architectures: [arm64]
      Tracing: PassThrough
    Metadata:
      BuildMethod: makefile

function/Makefile

build-OjichatFunction:
	GOOS=linux GOARCH=arm64 go build -tags lambda.norpc -o bootstrap
	cp ./bootstrap $(ARTIFACTS_DIR)/.

Makefileの内容は環境依存になるため本記事では厳密に動作検証していませんのでご了承ください。
Ubuntu環境で上記内容に対しsam buildが通ることまでは確認しています。

ちなみにビルドしたバイナリファイルは以下のサイズ感でした。

条件 ファイルサイズ
通常のビルド(arm64, lambda.norpcタグ無し) 21.6MB
Makefileビルド(arm64, lambda.norpcタグ有り) 19.9MB

最後に

以上となります。

簡単なアプリケーションだったので手順としてもあっさり目の内容になりました。
本記事の内容が皆さんの役に立てば幸いです。