Skip to content

Commit

Permalink
add a new FirelensAsyncEnabled config option
Browse files Browse the repository at this point in the history
  • Loading branch information
singholt committed Jan 24, 2025
1 parent be04138 commit 13df33a
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ additional details on each available environment variable.
| `ECS_DYNAMIC_HOST_PORT_RANGE` | `100-200` | This specifies the dynamic host port range that the agent uses to assign host ports from, for container ports mapping. If there are no available ports in the range for containers, including customer containers and Service Connect Agent containers (if Service Connect is enabled), service deployments would fail. | Defined by `/proc/sys/net/ipv4/ip_local_port_range` | `49152-65535` |
| `ECS_TASK_PIDS_LIMIT` | `100` | Specifies the per-task pids limit cgroup setting for each task launched on the container instance. This setting maps to the pids.max cgroup setting at the ECS task level. See https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#pid. If unset, pids will be unlimited. Min value is 1 and max value is 4194304 (4*1024*1024) | `unset` | Not Supported on Windows |
| `ECS_EBSTA_SUPPORTED` | `true` | Whether to use the container instance with EBS Task Attach support. This variable is set properly by ecs-init. Its value indicates if correct environment to support EBS volumes by instance has been set up or not. ECS only schedules EBSTA tasks if this feature is supported by the platform type. Check [EBS Volume considerations](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ebs-volumes.html#ebs-volume-considerations) for other EBS support details | `true` | Not Supported on Windows |
| `ECS_ENABLE_FIRELENS_ASYNC` | `true` | Whether the log driver connects to the Firelens container in the background. | `true` | `true` |

Additionally, the following environment variable(s) can be used to configure the behavior of the ecs-init service. When using ECS-Init, all env variables, including the ECS Agent variables above, are read from path `/etc/ecs/ecs.config`:
| Environment Variable Name | Example Value(s) | Description | Default value |
Expand Down
1 change: 1 addition & 0 deletions agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ func environmentConfig() (Config, error) {
WarmPoolsSupport: parseBooleanDefaultFalseConfig("ECS_WARM_POOLS_CHECK"),
DynamicHostPortRange: parseDynamicHostPortRange("ECS_DYNAMIC_HOST_PORT_RANGE"),
TaskPidsLimit: parseTaskPidsLimit(),
FirelensAsyncEnabled: parseBooleanDefaultTrueConfig("ECS_ENABLE_FIRELENS_ASYNC"),
}, err
}

Expand Down
43 changes: 43 additions & 0 deletions agent/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func TestEnvironmentConfig(t *testing.T) {
setTestEnv("ECS_HOST_DATA_DIR", "/etc/ecs/")
setTestEnv("ECS_CGROUP_CPU_PERIOD", "10ms")
setTestEnv("ECS_VOLUME_PLUGIN_CAPABILITIES", "[\"efsAuth\"]")
setTestEnv("ECS_ENABLE_FIRELENS_ASYNC", "true")

conf, err := environmentConfig()
assert.NoError(t, err)
Expand Down Expand Up @@ -224,6 +225,7 @@ func TestEnvironmentConfig(t *testing.T) {
assert.True(t, conf.ShouldExcludeIPv6PortBinding.Enabled(), "Wrong value for ShouldExcludeIPv6PortBinding")
assert.False(t, conf.WarmPoolsSupport.Enabled(), "Wrong value for WarmPoolsSupport")
assert.Equal(t, "200-300", conf.DynamicHostPortRange)
assert.True(t, conf.FirelensAsyncEnabled.Enabled())
}

func TestTrimWhitespaceWhenCreating(t *testing.T) {
Expand Down Expand Up @@ -975,6 +977,47 @@ func TestExternalConfigMissingRegion(t *testing.T) {
assert.Error(t, err)
}

func TestFirelensAsyncEnabled(t *testing.T) {
testCases := []struct {
name string
envVarValue string
expectedResult bool
}{
{
name: "default, env var not set",
envVarValue: "",
expectedResult: true,
},
{
name: "explicitly disabled",
envVarValue: "false",
expectedResult: false,
},
{
name: "explicitly enabled",
envVarValue: "true",
expectedResult: true,
},
{
name: "env var has invalid value",
envVarValue: "foo",
expectedResult: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("AWS_DEFAULT_REGION", "us-west-2")
if tc.envVarValue != "" {
t.Setenv("ECS_ENABLE_FIRELENS_ASYNC", tc.envVarValue)
}
cfg, err := NewConfig(ec2.NewBlackholeEC2MetadataClient())
assert.NoError(t, err)
assert.Equal(t, tc.expectedResult, cfg.FirelensAsyncEnabled.Enabled())
})
}
}

func setTestRegion() func() {
return setTestEnv("AWS_DEFAULT_REGION", "us-west-2")
}
Expand Down
1 change: 1 addition & 0 deletions agent/config/config_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func DefaultConfig() Config {
CSIDriverSocketPath: defaultCSIDriverSocketPath,
NodeStageTimeout: nodeStageTimeout,
NodeUnstageTimeout: nodeUnstageTimeout,
FirelensAsyncEnabled: BooleanDefaultTrue{Value: ExplicitlyEnabled},
}
}

Expand Down
1 change: 1 addition & 0 deletions agent/config/config_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ func DefaultConfig() Config {
CSIDriverSocketPath: defaultCSIDriverSocketPath,
NodeStageTimeout: nodeStageTimeout,
NodeUnstageTimeout: nodeUnstageTimeout,
FirelensAsyncEnabled: BooleanDefaultTrue{Value: ExplicitlyEnabled},
}
}

Expand Down
20 changes: 10 additions & 10 deletions agent/config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,45 +244,45 @@ func parseAdditionalLocalRoutes(errs []error) ([]cniTypes.IPNet, []error) {
}

func parseBooleanDefaultFalseConfig(envVarName string) BooleanDefaultFalse {
boolDefaultFalseCofig := BooleanDefaultFalse{Value: NotSet}
boolDefaultFalseConfig := BooleanDefaultFalse{Value: NotSet}
configString := strings.TrimSpace(os.Getenv(envVarName))
if configString == "" {
// if intentionally not set, do not add warning log
return boolDefaultFalseCofig
return boolDefaultFalseConfig
}

res, err := strconv.ParseBool(configString)
if err == nil {
if res {
boolDefaultFalseCofig.Value = ExplicitlyEnabled
boolDefaultFalseConfig.Value = ExplicitlyEnabled
} else {
boolDefaultFalseCofig.Value = ExplicitlyDisabled
boolDefaultFalseConfig.Value = ExplicitlyDisabled
}
} else {
seelog.Warnf("Invalid format for \"%s\", expected a boolean. err %v", envVarName, err)
}
return boolDefaultFalseCofig
return boolDefaultFalseConfig
}

func parseBooleanDefaultTrueConfig(envVarName string) BooleanDefaultTrue {
boolDefaultTrueCofig := BooleanDefaultTrue{Value: NotSet}
boolDefaultTrueConfig := BooleanDefaultTrue{Value: NotSet}
configString := strings.TrimSpace(os.Getenv(envVarName))
if configString == "" {
// if intentionally not set, do not add warning log
return boolDefaultTrueCofig
return boolDefaultTrueConfig
}

res, err := strconv.ParseBool(configString)
if err == nil {
if res {
boolDefaultTrueCofig.Value = ExplicitlyEnabled
boolDefaultTrueConfig.Value = ExplicitlyEnabled
} else {
boolDefaultTrueCofig.Value = ExplicitlyDisabled
boolDefaultTrueConfig.Value = ExplicitlyDisabled
}
} else {
seelog.Warnf("Invalid format for \"%s\", expected a boolean. err %v", envVarName, err)
}
return boolDefaultTrueCofig
return boolDefaultTrueConfig
}

func parseTaskMetadataThrottles() (int, int) {
Expand Down
4 changes: 4 additions & 0 deletions agent/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,4 +394,8 @@ type Config struct {

// NodeUnstageTimeout is the amount of time to wait for unstaging an EBS TA volume
NodeUnstageTimeout time.Duration

// FirelensAsyncEnabled specifies whether the agent should enable the async connect option of the
// fluentd log driver. Ref: https://docs.docker.com/engine/logging/drivers/fluentd/#fluentd-async
FirelensAsyncEnabled BooleanDefaultTrue
}
5 changes: 3 additions & 2 deletions agent/engine/docker_task_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2081,7 +2081,8 @@ func (engine *DockerTaskEngine) createContainer(task *apitask.Task, container *a
return metadata
}

func getFirelensLogConfig(task *apitask.Task, container *apicontainer.Container, hostConfig *dockercontainer.HostConfig, cfg *config.Config) dockercontainer.LogConfig {
func getFirelensLogConfig(task *apitask.Task, container *apicontainer.Container,
hostConfig *dockercontainer.HostConfig, cfg *config.Config) dockercontainer.LogConfig {
fields := strings.Split(task.Arn, "/")
taskID := fields[len(fields)-1]
tag := fmt.Sprintf(fluentTagDockerFormat, container.Name, taskID)
Expand All @@ -2092,7 +2093,7 @@ func getFirelensLogConfig(task *apitask.Task, container *apicontainer.Container,
logConfig.Config = make(map[string]string)
logConfig.Config[logDriverTag] = tag
logConfig.Config[logDriverFluentdAddress] = fluentd
logConfig.Config[logDriverAsyncConnect] = strconv.FormatBool(true)
logConfig.Config[logDriverAsyncConnect] = strconv.FormatBool(cfg.FirelensAsyncEnabled.Enabled())
logConfig.Config[logDriverSubSecondPrecision] = strconv.FormatBool(true)
if bufferLimitExists {
logConfig.Config[logDriverBufferLimit] = bufferLimit
Expand Down
69 changes: 69 additions & 0 deletions agent/engine/docker_task_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5034,3 +5034,72 @@ func TestSetRegistryCredentials(t *testing.T) {
})
}
}

func TestGetFirelensConfigWithAsyncEnabledConfigOption(t *testing.T) {
rawHostConfigInput := &dockercontainer.HostConfig{
LogConfig: dockercontainer.LogConfig{
Type: "awsfirelens",
Config: map[string]string{
"log-driver-buffer-limit": "10000",
},
},
}

rawHostConfig, err := json.Marshal(&rawHostConfigInput)
require.NoError(t, err)
hostConfig := func() *string {
s := string(rawHostConfig)
return &s
}()

appContainer := &apicontainer.Container{
Name: "app",
DockerConfig: apicontainer.DockerConfig{
HostConfig: hostConfig,
},
}

firelensContainer := &apicontainer.Container{
Name: "firelens",
FirelensConfig: &apicontainer.FirelensConfig{
Type: "fluentbit",
},
}

task := &apitask.Task{
Arn: "arn:aws:ecs:region:account-id:task/task-id",
Containers: []*apicontainer.Container{
appContainer,
firelensContainer,
},
}

testCases := []struct {
name string
isFirelensAsyncEnabled bool
}{
{
name: "async enabled",
isFirelensAsyncEnabled: true,
},
{
name: "async disabled",
isFirelensAsyncEnabled: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Initialize config with the appropriate FirelensAsyncEnabled value as per the test case.
asyncEnabled := config.BooleanDefaultTrue{Value: config.ExplicitlyDisabled}
if tc.isFirelensAsyncEnabled {
asyncEnabled = config.BooleanDefaultTrue{Value: config.ExplicitlyEnabled}
}
cfg := &config.Config{
FirelensAsyncEnabled: asyncEnabled,
}
logConfig := getFirelensLogConfig(task, appContainer, rawHostConfigInput, cfg)
assert.Equal(t, tc.isFirelensAsyncEnabled, logConfig.Config[logDriverAsyncConnect])
})
}
}

0 comments on commit 13df33a

Please sign in to comment.