シ〜らかんす

プログラミングとか、カメラとか。

Go gin & Web Adapterで動作するLambdaからDatadogのトレースを送信する

概要

AWS Web Adapterというものがあり、ウェブAPIのコードをそのままにサーバーレス化することができる。

aws.amazon.com

これいいじゃんということで、GoのGinで動くウェブAPIをサーバーレス化したのだが、以前のECS Fargate環境では正常に動作していたDatadogのトレース送信が動かなくなった。

ハマった末にやり方が分かったので、Lambda Web Adapter & Gin の構成でウェブAPIを動かす際にDatadogのトレースを正常に送る方法をお伝えする。

Web Adapterを使っていない場合はどうやってトレースを送るのか?

まずは、Web Adapterを使っていない通常のLambdaの場合だとどのようにDatadogトレースを送るのか紹介する。

Dockerfileに以下のコードを書き、datadog extensionをコンテナイメージに含める。

COPY --from=public.ecr.aws/datadog/lambda-extension:latest /opt/. /opt/

datadog-lambda-goのライブラリをインストールし、

go get github.com/DataDog/datadog-lambda-go

ddlambda.WrapFunction を仕込む。

func main() {
  // Wrap your lambda handler
  lambda.Start(ddlambda.WrapFunction(myHandler, nil))
}

func myHandler(ctx context.Context, event MyEvent) (string, error) {
  // lambdaの処理をここに書く
}

このやり方は公式ドキュメントにも記載があるし、特に問題なく動かせると思う。(※ 2023年4月現在、英語でないとcontainer imageの場合のやり方が出ないので注意)

docs.datadoghq.com

Web Adapterを使っている場合はどうなるか?

通常、コンテナイメージのLambdaを使う場合、上記のように lambda.Start がエントリポイントとなる。 しかし今回は、Web Adapterを使っているので、アプリケーションのコードはginを使った純粋なweb APIのコードになり、 lambda.Start をどこにも書いておらず、ddlambda.WrapFunctionを仕込むことができない。

ではどうやるのかというと、Datadogのトレーサーをスタートするときに、以下のようにオプションを入れると良い。

tracer.Start(
        tracer.WithEnv("環境名(stagingやproduction)を入れる"),
        tracer.WithService("Datadog上で扱われるサービス名を入れる"),
        tracer.WithLambdaMode(false), // trueにするとstdoutにtraceが出力され、datadog agentに送信されなくなる
        tracer.WithGlobalTag("_dd.origin", "lambda"),
    )
    defer tracer.Stop()

ECSからLambdaにginのAPIを移した際、当初はtracer.WithLambdaMode(false)tracer.WithGlobalTag("_dd.origin", "lambda") を入れておらず、トレースがDatadogに送信されてこなかった。

コードのコメントにも書いた通り、tracer.WithLambdaMode がtrueだとトレースがstdoutに出力され、datadog agentに送信されなくなる。

実際にはLambdaで動かしているのに、WithLambdaModeをfalseにするのは奇妙だが、Datadogのライブラリの実装を追っていくと確かにそうしている。

github.com

まとめ

Web Adapterを使うことで簡単に既存のAPIをLambdaに載せれるし、ローカルでも簡単に動かしやすくなる。 Datadogとの連携がドキュメントにも書いておらず難しかったが、これで解決できたのでよかった。