diff --git a/modules/ecs_fargate/README.md b/modules/ecs_fargate/README.md index f361015..4b9f0b6 100644 --- a/modules/ecs_fargate/README.md +++ b/modules/ecs_fargate/README.md @@ -255,6 +255,7 @@ No modules. | [dd\_is\_datadog\_dependency\_enabled](#input\_dd\_is\_datadog\_dependency\_enabled) | Whether the Datadog Agent container is a dependency for other containers | `bool` | `false` | no | | [dd\_log\_collection](#input\_dd\_log\_collection) | Configuration for Datadog Log Collection |
object({
enabled = optional(bool, false)
fluentbit_config = optional(object({
registry = optional(string, "public.ecr.aws/aws-observability/aws-for-fluent-bit")
image_version = optional(string, "stable")
cpu = optional(number)
memory_limit_mib = optional(number)
is_log_router_essential = optional(bool, false)
is_log_router_dependency_enabled = optional(bool, false)
environment = optional(list(object({
name = string
value = string
})), [])
log_router_health_check = optional(object({
command = optional(list(string))
interval = optional(number)
retries = optional(number)
start_period = optional(number)
timeout = optional(number)
}),
{
command = ["CMD-SHELL", "exit 0"]
interval = 5
retries = 3
start_period = 15
timeout = 5
}
)
firelens_options = optional(object({
config_file_type = optional(string)
config_file_value = optional(string)
}))
log_driver_configuration = optional(object({
host_endpoint = optional(string, "http-intake.logs.datadoghq.com")
tls = optional(bool)
compress = optional(string)
service_name = optional(string)
source_name = optional(string)
message_key = optional(string)
}),
{
host_endpoint = "http-intake.logs.datadoghq.com"
}
)
mountPoints = optional(list(object({
sourceVolume : string,
containerPath : string,
readOnly : bool
})), [])
dependsOn = optional(list(object({
containerName : string,
condition : string
})), [])
}),
{
fluentbit_config = {
registry = "public.ecr.aws/aws-observability/aws-for-fluent-bit"
image_version = "stable"
log_driver_configuration = {
host_endpoint = "http-intake.logs.datadoghq.com"
}
}
}
)
})
|
{
"enabled": false,
"fluentbit_config": {
"is_log_router_essential": false,
"log_driver_configuration": {
"host_endpoint": "http-intake.logs.datadoghq.com"
}
}
}
| no | | [dd\_memory\_limit\_mib](#input\_dd\_memory\_limit\_mib) | Datadog Agent container memory limit in MiB | `number` | `null` | no | +| [dd\_readonly\_root\_filesystem](#input\_dd\_readonly\_root\_filesystem) | Datadog Agent container runs with read-only root filesystem enabled | `bool` | `false` | no | | [dd\_registry](#input\_dd\_registry) | Datadog Agent image registry | `string` | `"public.ecr.aws/datadog/agent"` | no | | [dd\_service](#input\_dd\_service) | The task service name. Used for tagging (UST) | `string` | `null` | no | | [dd\_site](#input\_dd\_site) | Datadog Site | `string` | `"datadoghq.com"` | no | diff --git a/modules/ecs_fargate/datadog.tf b/modules/ecs_fargate/datadog.tf index d22fda2..e2ce992 100644 --- a/modules/ecs_fargate/datadog.tf +++ b/modules/ecs_fargate/datadog.tf @@ -77,6 +77,27 @@ locals { } ] : [] + dd_agent_mount = concat( + local.apm_dsd_mount, + var.dd_readonly_root_filesystem ? [ + { + containerPath = "/etc/datadog-agent" + sourceVolume = "agent-config" + readOnly = false + }, + { + containerPath = "/tmp" + sourceVolume = "agent-tmp" + readOnly = false + }, + { + containerPath = "/opt/datadog-agent/run" + sourceVolume = "agent-run" + readOnly = false + } + ] : [] + ) + apm_socket_var = local.is_apm_socket_mount ? [ { name = "DD_TRACE_AGENT_URL" @@ -223,6 +244,18 @@ locals { ) ] + rofs_volumes = var.dd_readonly_root_filesystem ? [ + { + name = "agent-config" + }, + { + name = "agent-tmp" + }, + { + name = "agent-run" + } + ] : [] + # Volume configuration for task apm_dsd_volume = local.is_apm_dsd_volume ? [ { @@ -238,6 +271,7 @@ locals { modified_volumes = concat( [for k, v in coalesce(var.volumes, []) : v], + local.rofs_volumes, local.apm_dsd_volume, local.cws_volume, ) @@ -264,6 +298,10 @@ locals { name = "DD_INSTALL_INFO_INSTALLER_VERSION" value = local.install_info_installer_version }, + { + name = "DD_LOG_FILE" + value = "/opt/datadog-agent/run/logs" + } ] dynamic_env = [ @@ -308,52 +346,85 @@ locals { local.dd_environment, ) + dd_agent_dependency = concat( + var.dd_readonly_root_filesystem ? [ + { + condition = "SUCCESS" + containerName = "init-volume" + } + ] : [], + try(var.dd_log_collection.fluentbit_config.is_log_router_dependency_enabled, false) && local.dd_firelens_log_configuration != null ? local.log_router_dependency : [], + ) + # Datadog Agent container definition - dd_agent_container = [ - merge( + dd_agent_container = concat( + var.dd_readonly_root_filesystem ? [ { - name = "datadog-agent" - image = "${var.dd_registry}:${var.dd_image_version}" - essential = var.dd_essential - environment = local.dd_agent_env - dockerLabels = var.dd_docker_labels - cpu = var.dd_cpu - memory = var.dd_memory_limit_mib - secrets = var.dd_api_key_secret != null ? [ + cpu = 0 + memory = 128 + name = "init-volume" + image = "${var.dd_registry}:${var.dd_image_version}" + essential = false + readOnlyRootFilesystem = true + command = ["/bin/sh", "-c", "cp -vnR /etc/datadog-agent/* /agent-config/ && exit 0"] + mountPoints = [ { - name = "DD_API_KEY" - valueFrom = var.dd_api_key_secret.arn + sourceVolume = "agent-config" + containerPath = "/agent-config" + readOnly = false } - ] : [] - portMappings = [ - { - containerPort = 8125 - hostPort = 8125 - protocol = "udp" - }, - { - containerPort = 8126 - hostPort = 8126 - protocol = "tcp" + ] + } + ] : [], + [ + merge( + { + name = "datadog-agent" + image = "${var.dd_registry}:${var.dd_image_version}" + essential = var.dd_essential + environment = local.dd_agent_env + dockerLabels = var.dd_docker_labels + cpu = var.dd_cpu + memory = var.dd_memory_limit_mib + + readonlyRootFilesystem = var.dd_readonly_root_filesystem + secrets = var.dd_api_key_secret != null ? [ + { + name = "DD_API_KEY" + valueFrom = var.dd_api_key_secret.arn + } + ] : [] + portMappings = [ + { + containerPort = 8125 + hostPort = 8125 + protocol = "udp" + }, + { + containerPort = 8126 + hostPort = 8126 + protocol = "tcp" + } + ], + + mountPoints = local.dd_agent_mount, + logConfiguration = local.dd_firelens_log_configuration, + dependsOn = local.dd_agent_dependency + systemControls = [] + volumesFrom = [] + }, + try(var.dd_health_check.command == null, true) ? {} : { + healthCheck = { + command = var.dd_health_check.command + interval = var.dd_health_check.interval + timeout = var.dd_health_check.timeout + retries = var.dd_health_check.retries + startPeriod = var.dd_health_check.start_period } - ], - mountPoints = local.apm_dsd_mount, - logConfiguration = local.dd_firelens_log_configuration, - dependsOn = try(var.dd_log_collection.fluentbit_config.is_log_router_dependency_enabled, false) && local.dd_firelens_log_configuration != null ? local.log_router_dependency : [], - systemControls = [] - volumesFrom = [] - }, - try(var.dd_health_check.command == null, true) ? {} : { - healthCheck = { - command = var.dd_health_check.command - interval = var.dd_health_check.interval - timeout = var.dd_health_check.timeout - retries = var.dd_health_check.retries - startPeriod = var.dd_health_check.start_period } - } - ) - ] + ) + ] + ) dd_log_environment = var.dd_log_collection.fluentbit_config.environment != null ? var.dd_log_collection.fluentbit_config.environment : [] diff --git a/modules/ecs_fargate/main.tf b/modules/ecs_fargate/main.tf index 28213a6..16d189a 100644 --- a/modules/ecs_fargate/main.tf +++ b/modules/ecs_fargate/main.tf @@ -177,6 +177,10 @@ resource "aws_ecs_task_definition" "this" { condition = var.dd_log_collection.enabled == false || (var.dd_log_collection.enabled == true && local.is_linux == true) error_message = "Log collection is not supported on Windows. Please set `dd_log_collection.enabled` to `false`." } + precondition { + condition = var.dd_readonly_root_filesystem == false || (var.dd_readonly_root_filesystem == true && local.is_linux == true) + error_message = "Readonly root filesystem is only supported on Linux. Please set `dd_readonly_root_filesystem` to `false`." + } # Must provide only one of the two Datadog API key options precondition { condition = (var.dd_api_key == null && var.dd_api_key_secret != null) || (var.dd_api_key != null && var.dd_api_key_secret == null) diff --git a/modules/ecs_fargate/variables.tf b/modules/ecs_fargate/variables.tf index d5cc82e..9f91715 100644 --- a/modules/ecs_fargate/variables.tf +++ b/modules/ecs_fargate/variables.tf @@ -65,6 +65,13 @@ variable "dd_is_datadog_dependency_enabled" { nullable = false } +variable "dd_readonly_root_filesystem" { + description = "Datadog Agent container runs with read-only root filesystem enabled" + type = bool + default = false + nullable = false +} + variable "dd_health_check" { description = "Datadog Agent health check configuration" type = object({ diff --git a/smoke_tests/ecs_fargate/all-dd-disabled.tf b/smoke_tests/ecs_fargate/all-dd-disabled.tf index 46aab76..ef913ea 100644 --- a/smoke_tests/ecs_fargate/all-dd-disabled.tf +++ b/smoke_tests/ecs_fargate/all-dd-disabled.tf @@ -17,6 +17,7 @@ module "dd_task_all_dd_disabled" { dd_tags = "team:cont-p, owner:container-monitoring" dd_essential = true dd_is_datadog_dependency_enabled = false + dd_readonly_root_filesystem = false dd_environment = [] diff --git a/smoke_tests/ecs_fargate/all-dd-inputs.tf b/smoke_tests/ecs_fargate/all-dd-inputs.tf index 54f1951..2da83cd 100644 --- a/smoke_tests/ecs_fargate/all-dd-inputs.tf +++ b/smoke_tests/ecs_fargate/all-dd-inputs.tf @@ -17,6 +17,7 @@ module "dd_task_all_dd_inputs" { dd_tags = "team:cont-p, owner:container-monitoring" dd_essential = true dd_is_datadog_dependency_enabled = true + dd_readonly_root_filesystem = true dd_environment = [ { diff --git a/smoke_tests/ecs_fargate/all-windows.tf b/smoke_tests/ecs_fargate/all-windows.tf index a0946bb..0ac9526 100644 --- a/smoke_tests/ecs_fargate/all-windows.tf +++ b/smoke_tests/ecs_fargate/all-windows.tf @@ -16,6 +16,8 @@ module "dd_task_all_windows" { dd_site = var.dd_site dd_service = var.dd_service + dd_readonly_root_filesystem = false + dd_apm = { enabled = true } diff --git a/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf b/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf index e5f66d6..1bda78d 100644 --- a/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf +++ b/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf @@ -18,6 +18,8 @@ module "dd_task_apm_dsd_tcp_udp" { dd_tags = "team:cont-p, owner:container-monitoring" dd_essential = true + dd_readonly_root_filesystem = true + dd_dogstatsd = { enabled = true, socket_enabled = false, diff --git a/smoke_tests/ecs_fargate/logging-only.tf b/smoke_tests/ecs_fargate/logging-only.tf index 1caa355..12015e5 100644 --- a/smoke_tests/ecs_fargate/logging-only.tf +++ b/smoke_tests/ecs_fargate/logging-only.tf @@ -15,6 +15,8 @@ module "dd_task_logging_only" { dd_service = var.dd_service dd_essential = true + dd_readonly_root_filesystem = false + dd_dogstatsd = { enabled = false, } diff --git a/smoke_tests/ecs_fargate/ust-docker-labels.tf b/smoke_tests/ecs_fargate/ust-docker-labels.tf index d614c58..592db4b 100644 --- a/smoke_tests/ecs_fargate/ust-docker-labels.tf +++ b/smoke_tests/ecs_fargate/ust-docker-labels.tf @@ -20,6 +20,7 @@ module "dd_task_ust_docker_labels" { dd_essential = true dd_is_datadog_dependency_enabled = true + dd_readonly_root_filesystem = true dd_log_collection = { enabled = true, diff --git a/tests/all_dd_disabled_test.go b/tests/all_dd_disabled_test.go index d44efa3..2b8963e 100644 --- a/tests/all_dd_disabled_test.go +++ b/tests/all_dd_disabled_test.go @@ -59,8 +59,7 @@ func (s *ECSFargateSuite) TestAllDDDisabled() { s.Equal(int32(3), *agentContainer.HealthCheck.Retries, "Agent health check retries should be 3") s.Equal(int32(60), *agentContainer.HealthCheck.StartPeriod, "Agent health check start period should be 60") - // Verify no mount points (apm/dsd volumes should not be present) - s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent when features are disabled") + s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points when features are disabled") // Test dummy container dummyContainer, found := GetContainer(containers, "dummy-container") diff --git a/tests/all_dd_inputs_test.go b/tests/all_dd_inputs_test.go index a03114b..b198ad3 100644 --- a/tests/all_dd_inputs_test.go +++ b/tests/all_dd_inputs_test.go @@ -25,7 +25,11 @@ func (s *ECSFargateSuite) TestAllDDInputs() { err := json.Unmarshal([]byte(task["container_definitions"]), &containers) s.NoError(err, "Failed to parse container definitions") - s.Equal(6, len(containers), "Expected 6 containers in the task definition") + s.Equal(7, len(containers), "Expected 6 containers in the task definition") + + initContainer, found := GetContainer(containers, "init-volume") + s.True(found, "Container init-volume not found in definitions") + AssertMountPoint(s.T(), initContainer, MountInitVolume) // Test Agent Container agentContainer, found := GetContainer(containers, "datadog-agent") @@ -37,6 +41,9 @@ func (s *ECSFargateSuite) TestAllDDInputs() { AssertPortMapping(s.T(), agentContainer, PortUDP) AssertPortMapping(s.T(), agentContainer, PortTCP) AssertMountPoint(s.T(), agentContainer, MountDdSocket) + AssertMountPoint(s.T(), agentContainer, MountAgentConfig) + AssertMountPoint(s.T(), agentContainer, MountAgentTmp) + AssertMountPoint(s.T(), agentContainer, MountAgentRun) AssertContainerDependency(s.T(), agentContainer, DependencyLogRouter) expectedAgentEnvvars := map[string]string{ diff --git a/tests/apm_dsd_tcp_udp_test.go b/tests/apm_dsd_tcp_udp_test.go index 1a361bb..3f722e7 100644 --- a/tests/apm_dsd_tcp_udp_test.go +++ b/tests/apm_dsd_tcp_udp_test.go @@ -26,7 +26,7 @@ func (s *ECSFargateSuite) TestApmDsdTcpUdp() { err := json.Unmarshal([]byte(task["container_definitions"]), &containers) s.NoError(err, "Failed to parse container definitions") - s.Equal(3, len(containers), "Expected 3 containers in the task definition") + s.Equal(4, len(containers), "Expected 3 containers in the task definition") // Test Agent Container agentContainer, found := GetContainer(containers, "datadog-agent") @@ -60,8 +60,11 @@ func (s *ECSFargateSuite) TestApmDsdTcpUdp() { } AssertNotEnvVars(s.T(), agentContainer, disabledSocketEnvVars) - // Verify no mount points (as sockets are not used) - s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent when socket is disabled") + s.Equal(3, len(agentContainer.MountPoints), "Expected 3 mount points when socket is disabled") + // Verify none are apm/dsd volume mount points + for _, mountPoint := range agentContainer.MountPoints { + s.NotEqual("dd-socket", *mountPoint.SourceVolume, "Mount point should not be dd-socket") + } // Test DogStatsD App Container dogstatsdContainer, found := GetContainer(containers, "datadog-dogstatsd-app") diff --git a/tests/logging_only_test.go b/tests/logging_only_test.go index 92eba35..f9218e4 100644 --- a/tests/logging_only_test.go +++ b/tests/logging_only_test.go @@ -73,8 +73,7 @@ func (s *ECSFargateSuite) TestLoggingOnly() { s.Equal("datadog-log-router", *agentContainer.DependsOn[0].ContainerName, "Agent should depend on datadog-log-router") s.Equal(types.ContainerConditionHealthy, agentContainer.DependsOn[0].Condition, "Agent should depend on log router being healthy") - // Verify no mount points - s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent") + s.Equal(0, len(agentContainer.MountPoints), "Expected 2 mount points for datadog-agent") // Test Log Router Container logRouterContainer, found := GetContainer(containers, "datadog-log-router") diff --git a/tests/ust_docker_labels_test.go b/tests/ust_docker_labels_test.go index 6bde250..1cbd049 100644 --- a/tests/ust_docker_labels_test.go +++ b/tests/ust_docker_labels_test.go @@ -25,7 +25,7 @@ func (s *ECSFargateSuite) TestUSTDockerLabels() { err := json.Unmarshal([]byte(task["container_definitions"]), &containers) s.NoError(err, "Failed to parse container definitions") - s.Equal(4, len(containers), "Expected 4 containers in the task definition (1 app container + 3 agent sidecar)") + s.Equal(5, len(containers), "Expected 4 containers in the task definition (1 app container + 3 agent sidecar)") // Expected UST docker labels that should be present on all application containers expectedUSTLabels := map[string]string{ diff --git a/tests/utils.go b/tests/utils.go index d372f3b..2b679d7 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -16,6 +16,10 @@ import ( var ( MountDdSocket = types.MountPoint{SourceVolume: aws.String("dd-sockets"), ContainerPath: aws.String("/var/run/datadog"), ReadOnly: aws.Bool(false)} MountCWS = types.MountPoint{SourceVolume: aws.String("cws-instrumentation-volume"), ContainerPath: aws.String("/cws-instrumentation-volume"), ReadOnly: aws.Bool(false)} + MountInitVolume = types.MountPoint{SourceVolume: aws.String("agent-config"), ContainerPath: aws.String("/agent-config"), ReadOnly: aws.Bool(false)} + MountAgentConfig = types.MountPoint{SourceVolume: aws.String("agent-config"), ContainerPath: aws.String("/etc/datadog-agent"), ReadOnly: aws.Bool(false)} + MountAgentTmp = types.MountPoint{SourceVolume: aws.String("agent-tmp"), ContainerPath: aws.String("/tmp"), ReadOnly: aws.Bool(false)} + MountAgentRun = types.MountPoint{SourceVolume: aws.String("agent-run"), ContainerPath: aws.String("/opt/datadog-agent/run"), ReadOnly: aws.Bool(false)} PortTCP = types.PortMapping{ContainerPort: aws.Int32(8126), HostPort: aws.Int32(8126), Protocol: types.TransportProtocolTcp} PortUDP = types.PortMapping{ContainerPort: aws.Int32(8125), HostPort: aws.Int32(8125), Protocol: types.TransportProtocolUdp} DependencyAgent = types.ContainerDependency{ContainerName: aws.String("datadog-agent"), Condition: types.ContainerConditionHealthy}