吾輩は人間である
前書き
最近新規で立ち上げたアプリケーションに外形監視を導入したいと考えており、N社のサービスにするか、AWS内で完結するか悩んでいましたがDatadogを採用することに決めました。
ネットで調べてもNext.js + ECS Fargateの構成でのDatadog導入方法に関する情報があまり見つからなかったため、実際に行った作業手順をメモとして残します。
やりたいこと
アプリケーションのログとAPM両方をDatadogに送信したい。
既存のインフラはCDKで管理しているため、CDKを活用して適切なアーキテクチャを構築したい。
CDKの変更(差分のみ)
DatadogのAPIキーを保存するSecrets Managerを追加する。
ECSから接続する際にキーの設定が必要になりますが、Datadogの「Organization Settings」→「API Keys」からキーを取得・更新できます。
また、初回ECS接続時の待機画面でも、設定ファイル内にAPIキーが表示されます。
Secrets Managerデプロイできたら、取得したキーを追加しましょう。
// Datadog API キー用シークレットの作成
const datadogSecret = new secretsmanager.Secret(this, 'DatadogSecret', {
secretName: `${props.envName}-${props.projectName}-datadog-api-key`,
description: 'Datadog API Key - Update manually after deployment',
secretStringValue: cdk.SecretValue.unsafePlainText('placeholder-update-manually'),
});
datadogSecret.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);
// タスク実行ロールにDatadogシークレットへのアクセス権限を付与
datadogSecret.grantRead(executionRole);
メインコンテナの設定
ECSのログをそのままDatadogに送信する方がCloudWatch経由より安価なため、FireLensを使用します。
const clientContainer = serviceTaskDefinition
.addContainer('client', {
image: appImage,
logging: ecs.LogDrivers.firelens({
options: {
Name: 'datadog',
Host: 'http-intake.logs.ap1.datadoghq.com',
TLS: 'on',
dd_service: 'service_name',
dd_source: 'nodejs',
dd_tags: `env:${props.envName}`,
provider: 'ecs',
},
secretOptions: {
apikey: ecs.Secret.fromSecretsManager(datadogSecret),
},
}),
environment: {
// ... 他の環境変数
DD_ENV: props.envName,
DD_SERVICE: 'service_name',
DD_VERSION: props.imageTag,
DD_LOGS_INJECTION: 'true',
DD_AGENT_HOST: '127.0.0.1',
OTEL_TRACES_EXPORTER: 'otlp',
OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',
OTEL_EXPORTER_OTLP_ENDPOINT: 'http://127.0.0.1:4318',
OTEL_SERVICE_NAME: 'service_name',
OTEL_RESOURCE_ATTRIBUTES: `deployment.environment=${props.envName},service.version=${props.imageTag}`,
},
// ...
});
Datadog Agentサイドカーコンテナ
DD_SITE
は使用しているDatadogのリージョンに合わせて設定する必要があります。
設定が間違っているとログが送信されないため注意が必要です。
東京リージョンの場合、ap1.datadoghq.com
になります。
const datadogLogGroup = new logs.LogGroup(this, 'DatadogLogGroup', {
logGroupName: `/aws/ecs/datadog/${props.envName}`,
retention: logs.RetentionDays.THREE_MONTHS,
removalPolicy: props.envName === 'prd' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
});
const datadogContainer = serviceTaskDefinition.addContainer('datadog-agent', {
image: ecs.ContainerImage.fromRegistry('public.ecr.aws/datadog/agent:latest'),
memoryLimitMiB: 512,
containerName: 'datadog',
essential: false,
logging: ecs.LogDriver.awsLogs({
streamPrefix: 'datadog',
logGroup: datadogLogGroup,
}),
secrets: {
DD_API_KEY: ecs.Secret.fromSecretsManager(datadogSecret),
},
environment: {
ECS_FARGATE: 'true',
DD_APM_ENABLED: 'true',
DD_APM_NON_LOCAL_TRAFFIC: 'true',
DD_SITE: 'ap1.datadoghq.com',
DD_LOGS_ENABLED: 'true',
DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL: 'true',
},
});
datadogContainer.addEnvironment(
'DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_HTTP_ENDPOINT',
'0.0.0.0:4318',
);
// APM用ポートマッピング追加
datadogContainer.addPortMappings({
containerPort: 4318,
protocol: ecs.Protocol.TCP,
});
FireLens Log Router設定
serviceTaskDefinition.addFirelensLogRouter('FirelensLogRouter', {
image: ecs.ContainerImage.fromRegistry('amazon/aws-for-fluent-bit:stable'),
essential: false,
memoryReservationMiB: 50,
firelensConfig: {
type: ecs.FirelensLogRouterType.FLUENTBIT,
options: {
enableECSLogMetadata: true,
configFileType: ecs.FirelensConfigFileType.FILE,
configFileValue: '/fluent-bit/configs/parse-json.conf',
},
},
logging: ecs.LogDriver.awsLogs({
streamPrefix: 'firelens',
logGroup: logGroup,
}),
});
Next.js側の変更
dd-trace
を追加します。このプロジェクトではBunを使用しているため、以下のように追加します。
ちなみに、Bunは多才でおすすめです。
% bun add dd-trace
App Routerを使用しているため、src/
配下にtracer.tsを作成し、以下のコードを記述します。
import tracer from 'dd-trace';
tracer.init({
logInjection: true,
version: process.env.DD_VERSION,
env: process.env.DD_ENV,
service: process.env.DD_SERVICE,
});
export default tracer;
作成したファイルを同じ階層にinstrumentation.tsを作って、中からimportします。instrumentation.tsはNext.jsのアプリケーション起動時に一度だけ実行されるファイルで、計測や監視の初期化によく使用されます。
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./tracer');
}
}
これらの設定で、Next.jsのアプリからのログ、APMをDatadogへ転送することができました。
参考資料