
こんにちは!もりやまです🤟
先日、AWS LambdaとDynamoDBで一つ新しいアプリケーションを構築しようとしたところ、Lambdaに置いたGoからDynamoDBに接続できないという問題に遭遇しました。今回は、その問題の解決過程を備忘録として残しておきます。
DynamoDBに接続しようとしたらタイムアウトしてしまう
LambdaはGoをDockerイメージでデプロイ。中身はシンプルにDynamoDBに接続。もう少し詳しく書くと、
- Goのバージョン1.20、フレームワークはEchoを使用
- APIGateway(REST API) + Lambda + DynamoDB構成
- DynamoDBはguregu/dynamoを使って処理
- RuntimeはDockerイメージで、Amazon Linux 2をベースに作成
- デプロイはServerlessFrameworkを使用
といった状態です。しかし、LambdaからDynamoDBに接続しようとすると、タイムアウトしてしまうという問題が発生しました。
少しシンプルな状態にして接続テストをしてみます。
package main
import (
"context"
"log"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
echoadapter "github.com/awslabs/aws-lambda-go-api-proxy/echo"
"github.com/labstack/echo/v4"
)
var (
echoLambda *echoadapter.EchoLambda
dynClient dynamodbiface.DynamoDBAPI
)
type User struct {
Email string `json:"Email"`
}
func init() {
e := echo.New()
e.GET("/test", testHandleRequest)
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(os.Getenv("AWS_REGION")),
}))
dynClient = dynamodb.New(sess)
echoLambda = echoadapter.New(e)
}
func testHandleRequest(c echo.Context) error {
user := User{Email: "text@example.com"}
av, err := dynamodbattribute.MarshalMap(user)
if err != nil {
log.Println("Error marshalling user:", err)
return err
}
input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String("TestDB"),
}
_, err = dynClient.PutItem(input)
if err != nil {
log.Println("Error calling PutItem:", err)
return err
}
result, err := dynClient.GetItem(&dynamodb.GetItemInput{
TableName: aws.String("TestDB"),
Key: map[string]*dynamodb.AttributeValue{
"Email": {
S: aws.String("text@example.com"),
},
},
})
if err != nil {
log.Println("Error calling GetItem:", err)
return err
}
var userRetrieved User
if err := dynamodbattribute.UnmarshalMap(result.Item, &userRetrieved); err != nil {
log.Println("Error unmarshalling result:", err)
return err
}
return c.JSON(200, userRetrieved)
}
func main() {
lambda.Start(Handler)
}
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return echoLambda.ProxyWithContext(ctx, req)
}
localではamazon/dynamodb-localを使っているのですが、こちらのコードで問題なく動作しています。ところが、実際のLambdaで動かしてみると、何のエラーメッセージもなく、単にタイムアウトするという問題が発生してしまいます。
最初はos.Getenv("AWS_REGION")
が取得できていないのかと思い、log.Println(os.Getenv("AWS_REGION"))
で確認してみましたが、ちゃんと取得できていました。
試しにRubyのコードでLamdnaからDynamoDBに接続してみる
そもそも自分の構成が何か間違った?と疑い、問題切り分けのために、RubyでDynamoDBに接続してみることにしました。
require 'json'
require 'aws-sdk-dynamodb'
def lambda_handler(event:, context:)
puts ENV['AWS_REGION']
dynamodb = Aws::DynamoDB::Client.new(region: ENV['AWS_REGION'])
table_name = 'TestDB'
item = {
'Email': "text@example.com",
'Attribute1Name': 'Attribute1Value',
'Attribute2Name': 'Attribute2Value'
}
params = {
table_name: table_name,
item: item
}
begin
dynamodb.put_item(params)
puts 'PutItem succeeded.'
{ statusCode: 200, body: JSON.generate('Hello from Lambda!') }
rescue Aws::DynamoDB::Errors::ServiceError => error
puts 'Unable to PutItem:'
puts error.message
{ statusCode: 503, body: JSON.generate('ERROR!') }
end
end
こちらは問題なく動作しました。なので、構成などに問題があるのではなく、Goのコード周りに問題があるということがわかりました。
aws/aws-sdk-goからaws/aws-sdk-go-v2への移行と、guregu/dynamoの対応状況
最初はguregu/dynamoパッケージが原因かな?と思いましたが、aws/aws-sdk-goを使ってもダメだったので、色々と調査を進めていくと、GoのAWS SDKがv1からv2に移行されたことがわかりました。
aws/aws-sdk-go-v2でほぼ同じコードを試したところ、今度は「tls: failed to verify certificate: x509: certificate signed by unknown authority」という明確なエラーメッセージが表示されました🥹
これはDockerfileにca-certificatesを追加することで解決できるので、無事にLambdaからDynamoDBへのデータ書き込みが可能になりました。元の接続できないのもこの問題か?と思ったのですが違いました。
なので、問題はv2に移行することで解決しそうです。
guregu/dynamoにはまだmainブランチでaws/aws-sdk-go-v2への対応が完了していなく、PullRequestが進行中といったところです。aws/aws-sdk-go-v2を直接使うことを考えましたが、guregu/dynamoのインターフェースが素晴らしいので、v2ブランチを使うことにしました。
今回は、LambdaからDynamoDBに接続できなかった時の備忘録でした。2023年12月にLambdaのGoランタイムが終わったりしているので、AWS周りはちゃんとバージョン確認しないといけませんね😅
それでは。