From eaaa174a7c8ad8123ab139c0dde99a50319257d0 Mon Sep 17 00:00:00 2001 From: Andrew Plummer Date: Thu, 21 Dec 2023 10:44:53 +0000 Subject: [PATCH] Support adding Datadog to Fargate tasks --- index.ts | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/index.ts b/index.ts index e075d6a..b8177c4 100644 --- a/index.ts +++ b/index.ts @@ -129,3 +129,211 @@ export class EcsDatadogDaemonService extends Construct { }); } } + +export interface AddDatadogToFargateTaskProps { + // Note: it is important that the value specified by this secret + // doesn't have a newline at the end. Otherwise, the firelens + // logging configuration will fail to send logs to Datadog. This + // is an easy mistake to introduce when the source for the secret + // is a secretsmanager Secret containing a single value, rather + // than key-value pairs. + datadogApiKeySecret: ecs.Secret; + // Defaults to datadoghq.com + datadogSite?: string; + agent?: { + // Defaults to false + enabled?: boolean; + // Defaults to public.ecr.aws/datadog/agent:latest + image?: ecs.ContainerImage; + // Defaults to latest + imageTag?: string; + // Defaults to 256 + memoryLimitMiB?: number; + // Defaults to unset + cpu?: number; + // Defaults to false + logToCloudWatch?: boolean; + apm?: { + // Defaults to false + enabled?: boolean; + // Defaults to 8126 + port?: number; + applicationEnvVars?: { + // Defaults to false + doNotSet?: boolean; + // Defaults to DD_AGENT_HOST + apmHostEnvVarName?: string; + // Defaults to DD_TRACE_AGENT_PORT + apmPortEnvVarName?: string; + // Defaults to DD_TRACE_ENABLED + apmTraceEnabledEnvVarName?: string; + }; + }; + statsd?: { + // Defaults to false + enabled?: boolean; + // Defaults to 8125 + port?: number; + applicationEnvVars?: { + // Defaults to false + doNotSet?: boolean; + // Defaults to STATSD_HOST + statsdHostEnvVarName?: string; + // Defaults to STATSD_PORT + statsdPortEnvVarName?: string; + }; + }; + }; + fireLensLogging?: { + // Defaults to false + enabled?: boolean; + // Defaults to unset + service?: string; + // Defaults to unset + source?: string; + // Defaults to unset + tags?: Record; + // Defaults to 256 + memoryLimitMiB?: number; + // Defaults to unset + cpu?: number; + // Defaults to public.ecr.aws/datadog/aws-for-fluent-bit:latest + image?: ecs.ContainerImage; + // Defaults to latest + imageTag?: string; + }; +} + +const formatTags = (tags: Record): string => { + const formattedTags = []; + for (const [key, value] of Object.entries(tags)) { + formattedTags.push(`${key}:${value}`); + } + return formattedTags.join(','); +}; + +export const addDatadogToFargateTask = (task: ecs.TaskDefinition, props: AddDatadogToFargateTaskProps) => { + const containerNames = []; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const containers = task.containers; + for (const container of containers) { + containerNames.push(container.containerName); + } + + if (props.agent?.enabled) { + task.addContainer('datadog-agent', { + image: props.agent?.image ?? ecs.ContainerImage.fromRegistry(`public.ecr.aws/datadog/agent:${props.agent?.imageTag ?? 'latest'}`), + memoryLimitMiB: props.agent?.memoryLimitMiB ?? 256, + ...(props.agent?.cpu + ? { + cpu: props.fireLensLogging?.cpu, + } + : {}), + ...(props.agent?.logToCloudWatch + ? { + logging: ecs.LogDrivers.awsLogs({ + streamPrefix: 'datadog-agent', + }), + } + : {}), + environment: { + ECS_FARGATE: 'true', + DD_SITE: props.datadogSite ?? 'datadoghq.com', + ...(props.agent?.apm?.enabled + ? { + DD_APM_ENABLED: 'true', + DD_APM_RECEIVER_PORT: String(props.agent?.apm?.port ?? '8126'), + } + : { + DD_APM_ENABLED: 'false', + }), + ...(props.agent?.statsd?.enabled + ? { + DD_USE_DOGSTATSD: 'true', + DD_DOGSTATSD_PORT: String(props.agent?.statsd?.port ?? '8125'), + } + : { + DD_USE_DOGSTATSD: 'false', + }), + }, + secrets: { + DD_API_KEY: props.datadogApiKeySecret, + }, + }); + + if (props.agent?.apm?.enabled && props.agent?.apm?.applicationEnvVars?.doNotSet !== true) { + for (const containerName of containerNames) { + const container = task.findContainer(containerName); + if (container) { + container.addEnvironment(props.agent?.apm.applicationEnvVars?.apmTraceEnabledEnvVarName ?? 'DD_TRACE_ENABLED', 'true'); + container.addEnvironment(props.agent?.apm.applicationEnvVars?.apmHostEnvVarName ?? 'DD_AGENT_HOST', 'localhost'); + container.addEnvironment(props.agent?.apm.applicationEnvVars?.apmPortEnvVarName ?? 'DD_TRACE_AGENT_PORT', String(props.agent?.apm?.port ?? '8126')); + } + } + } + + if (props.agent?.statsd?.enabled && props.agent?.statsd?.applicationEnvVars?.doNotSet !== true) { + for (const containerName of containerNames) { + const container = task.findContainer(containerName); + if (container) { + container.addEnvironment(props.agent?.statsd?.applicationEnvVars?.statsdHostEnvVarName ?? 'STATSD_HOST', 'localhost'); + container.addEnvironment(props.agent?.statsd?.applicationEnvVars?.statsdPortEnvVarName ?? 'STATSD_PORT', String(props.agent?.statsd?.port ?? '8125')); + } + } + } + } + + if (props.fireLensLogging?.enabled) { + task.addFirelensLogRouter('log_router', { + image: props.fireLensLogging?.image ?? ecs.ContainerImage.fromRegistry(`public.ecr.aws/datadog/aws-for-fluent-bit:${props.fireLensLogging?.imageTag ?? 'latest'}`), + memoryLimitMiB: props.fireLensLogging?.memoryLimitMiB ?? 256, + ...(props.fireLensLogging?.cpu + ? { + cpu: props.fireLensLogging?.cpu, + } + : {}), + firelensConfig: { + type: ecs.FirelensLogRouterType.FLUENTBIT, + options: { + enableECSLogMetadata: true, + }, + }, + }); + + const firelensLogDriver = ecs.LogDrivers.firelens({ + options: { + Name: 'datadog', + Host: `http-intake.logs.${props.datadogSite ?? 'datadoghq.com'}`, + TLS: 'on', + provider: 'ecs', + dd_message_key: 'log', + ...(props.fireLensLogging?.service + ? { + dd_service: props.fireLensLogging?.service, + } + : {}), + ...(props.fireLensLogging?.source + ? { + dd_source: props.fireLensLogging?.source, + } + : {}), + ...(props.fireLensLogging?.tags + ? { + dd_tags: formatTags(props.fireLensLogging?.tags), + } + : {}), + }, + secretOptions: { + apikey: props.datadogApiKeySecret, + }, + }); + + for (const containerName of containerNames) { + const container = task.findContainer(containerName); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + container.logDriverConfig = firelensLogDriver.bind(container, container); + } + } +};