
AWS LambdaにSplunk APMを組み込みSplunk Observability Cloud上でモニタリングできるようにしてみました
初めに
最近少し縁がありSplunk Observabilityの機能であるRUM/APMのデモ環境を立ち上げる機会がありました。
画面の見え方を確認するのであれば上記を実行することでよしなにminikube環境が立ち上がりAPM/RUMが実際どのようにSplunk Observability Cloud上で見えるか確認できるのですが、良くも悪くも全てセットアップされてしまうので実際に組み込み部分が見えないものとなっております。
個人的にはなんか見える!程だとイマイチ頭に染み込んだ気がしないので今回は実際にLambda関数に組み込んで理解を深めてみます。
Splunk APMとは
Splunk APMは名前の通りアプリケーションモニタリング(APM)を提供するSplunk Observabilityの機能の一つとなっております。
AWSに慣れている方であればAWS X-Rayが同立ち位置のサービスというとわかりやすいかもしれません。
従来の監視ではインフラベースで遅延等をモニタリングし問題を検出することが多く行われてきましたが、APMはその上に乗っているアプリケーションに焦点を置いたモニタリングとなります。
APMを使うことで実際にアプリの特定のメソッドがどの程度時間がかかっているか、別コンポーネントを叩いた際にアプリ目線でどの程度時間がかかっているか、どのコンポーネントでエラーが出ているかなどを取得しモニタリングすることが可能です。
また近年ではサービスがマイクロ化され機能ごとにコンポーネントが分割されることも増えてきましたが、APMを提供する多くのサービスでは「サービスマップ」(ものによって名称は異なるかも)という形で各コンポーネント間の依存等をグラフのように可視化し、より全体を見た上で個別の問題にブレイクダウンしていくことも可能となっております。
ちなみにSplunk公式から提供されているサンプル(最初に貼ったGithubのリンク先のもの)を構築するとこんな感じのサービスマップが確認できます。
(minikube上に作成されたkubernetesベースの分散アプリケーション)
この場合frontend
・checkoutservice
間の遅延が大きく、paymentservice
で特にエラーが発生している様子が見えます。
今回はそこまで追いませんがエンドポイント単位で見たり個別のコンポーネントにブレイクダウンして情報も見ることもできます。
提供されているレイヤー
AWS Lambda組み込み用のレイヤーとしては以下のページに記載がある通り、現時点では3種類提供されています。
基本的には上の方がセットアップは楽な代わりにLambda関数の負荷やオーバーヘッドが増え、下の方がLambda関数自体の負荷やオーバーヘッドは減るもののセットアップの手間が増えるという形のようです。
- オールインワン
- ドキュメント曰く推奨はこれ
- 各言語用のレイヤー + Collectorのレイヤー
- 言語用のレイヤー + EC2 Collector
- Lambda関数上には言語用のレイヤーのみ。EC2 Collecotrは別途EC2に構築
セットアップ的には1点目と2点目はレイヤーの種類が分かれるかどうかほどの違いでLambda関数上で完結するためあまり大きな違いはなさそうですが、3点目のみ外部にCollectorを構築しないといけないためかなり敷居が上がりそうです。
今回はお試しで「オールインワン」を利用してセットアップします。
トークンの取得
Lambda関数側のセットアップは後述しますが、Lambda関数からSplunk側にデータを送信するために認証トークンが必要になるので発行します。
画面の「Settings > Access Token」に遷移し「Create Token」を開きます。
デフォルトで既にトークンが生成されているためそちらを利用しても良いのですが共通で利用するとトークンの失効範囲も多く聞くなるので特定の単位で個別にトークンを発行するのが好ましいはずです。
デフォルトですでに組織トークンが発行されていますが、共通のトークンを利用すると何かあってトークンを失効させたい場合の影響が大きくなるため個別に発行します。
権限の選択がありますが、トークンの権限は以下に記載がある通りadmin
, power
, usage
, read_only
の4種があります。
どの権限を当てるかドキュメントに書いてなかったのですが実際に挙動ベースで確認する限りpower
,usage
の2つを当てる必要がありました。
画像上はpower
のみですが後で動作確認したら不足してたので別途追加しました(権限は作成後でも変更可能)。usage
だけにしても401 Errorが出たので両方必要そうです。
トークン自体を取得可能なユーザの権限を設定します。
最後にトークンの有効期限を選択し「Create」します。
少しして画面を更新するとトークンが発行され、赤枠部分をクリックするとクリップボードにトークンがコピーされますのでこちらを控えておきます。
Lambda関数への適用とデプロイ
ガイド付きセットアップもあるようですが、手動でLambdaレイヤーの追加といくつかの環境変数を埋め込む形でも対応できそうなので今回はAWS SAMでデプロイします。
ベースはAWS SAMのHello Worldテンプレート(nodejs 22.x)を利用しそれを拡張します。
SAMテンプレートは以下のとおりです。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
## 直接Strigで渡しているがSecrets Manager経由の方が良い
SplunkToken:
Type: String
NoEcho: True
SplunkRelm:
Type: String
Default: jp0
Globals:
Function:
+ Timeout: 60
Layers:
## REF: https://docs.splunk.com/observability/ja/gdi/get-data-in/serverless/aws/otel-lambda-layer/instrument-lambda-functions.html
- !Sub arn:aws:lambda:${AWS::Region}:254067382080:layer:splunk-apm:114
Environment:
+ Variables:
+ SPLUNK_REALM: !Ref SplunkRelm
+ SPLUNK_ACCESS_TOKEN: !Ref SplunkToken
+ AWS_LAMBDA_EXEC_WRAPPER: /opt/nodejs-otel-handler
+ OTEL_SERVICE_NAME: sam-app-splunk
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs22.x
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Metadata: # Manage esbuild properties
BuildMethod: esbuild
BuildProperties:
Minify: true
+ Target: "commonJS"
Sourcemap: true
EntryPoints:
- app.ts
タイムアウトに関してはデフォルトの3秒だとSplunk側のレイヤーの処理に間に合わずタイムアウトが発生していたので少し長めにしておくと良さそうです(5~10秒程度?)。
特に重いのは1回目なので初期化処理が重そうです。
デプロイは特に特別な処理は必要なくsam build && sam deploy
で問題ありません。アクセストークンは少し雑ですがCLI経由で渡してます。
sam build && sam deploy --parameter-overrides SplunkToken=xxxxx
Cannot redefine propertyではまった
順番が前後しますが上記のまま実行するとCannot redefine property: lambdaHandler
が発生します。
こちらについてドキュメントに記載がなくどうしたものか...と調べてみたのですがGithubの方にそれらしきissueが上がっておりどうもESモジュール側の仕様とのかみ合いのようです。
あまりこのあたりの仕様まで踏み込んで調整したことがないのもありこちらが最適な解決かわからないのですがハンドラー関数周りの調整と、tsconfigを以下のように調整します。
{
"compilerOptions": {
"target": "es2020",
"strict": true,
"preserveConstEnums": true,
"noEmit": true,
"sourceMap": false,
- "module":"es2015",
+ "module":"commonjs",
"moduleResolution":"node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
},
"exclude": ["node_modules", "**/*.test.ts"]
}
- expport const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
+ const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
return {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
} catch (err) {
console.log(err);
return {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
};
+ export = { lambdaHandler };
確認
curlで今回生成されたAPI Gatewayを叩いてみたところ無事表示されました。
サービス名はLambda関数の環境変数で設定したOTEL_SERVICE_NAME
の値です。
(app.LambdaHandeler
は色々試してた時の残骸です)
サービスマップもありますが単一コンポーネントな上にエラー等も出てないので流石にこれだけだと面白みはないですね...。
ログも見れるかな?と思ってconsole.debug()
を埋め込んでみましたが、どうやらそちらはSplunk Cloud or Enterpriseのライセンスが必要そうで今回はみられませんでした。
終わりに
今回AWS Lambdaの関数にSplunk APM用のLambdaレイヤーを組み込んでSplunk Observability Cloud上で見れるようにセットアップしてみました。
nodejs個別の問題でハマる部分はありましたが、セットアップだけでいえば結構シンプル、むしろ以前New Relicで組み込んだ時は専用のCLIを入れる必要があった分こちらの方がお手軽ではあります(あくまでセットアップ手順だけ、周辺の機能等は別として)。
今回はAPMを組み込んでみましたが、SplunkにはRUMもあるのでそちらも合わせて組み込んで見てみたいと思います。