Source code for ecs_composex.ecs.ecs_firelens.firelens_managed_sidecar_service

#  SPDX-License-Identifier: MPL-2.0
#  Copyright 2020-2022 John Mille <john@compose-x.io>

from __future__ import annotations

import re
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ecs_composex.ecs.ecs_family import ComposeFamily

from itertools import chain

from compose_x_common.compose_x_common import keyisset
from troposphere import If, NoValue, Ref, Sub
from troposphere.ecs import FirelensConfiguration

from ecs_composex.common.cfn_params import Parameter
from ecs_composex.common.logging import LOG
from ecs_composex.common.troposphere_tools import add_parameters
from ecs_composex.ecs.ecs_conditions import USE_FARGATE_CON_T
from ecs_composex.ecs.managed_sidecars import ManagedSidecar

FLUENT_BIT_IMAGE_PARAMETER = Parameter(
    "FluentBitAwsImage",
    Type="AWS::SSM::Parameter::Value<String>",
    Default="/aws/service/aws-for-fluent-bit/latest",
)

FLUENT_BIT_AGENT_NAME = "log_router"
DEFAULT_MEMORY_LIMIT = 64
DEFAULT_CPU_LIMIT = 0.1


[docs]def render_agent_config( family: ComposeFamily, api_health_enabled: bool = False, enable_prometheus: bool = False, memory_limits: int = DEFAULT_MEMORY_LIMIT, cpu_limits: float = DEFAULT_CPU_LIMIT, ) -> dict: if memory_limits > 512: LOG.warning(f"{family.name} - FireLens container memory defined exceeds 512MB") elif memory_limits < DEFAULT_MEMORY_LIMIT: LOG.warning( "{} - FireLens container memory defined is below the minimum requirement. " "Setting to {}MB".format(family.name, DEFAULT_MEMORY_LIMIT) ) memory_limits = DEFAULT_MEMORY_LIMIT config: dict = { "image": "public.ecr.aws/aws-observability/aws-for-firehose_destination.bit:latest", "deploy": { "resources": { "limits": {"cpus": cpu_limits, "memory": f"{memory_limits}M"}, "reservations": {"memory": "32M"}, }, }, "labels": {"container_name": "log_router"}, "logging": { "driver": "awslogs", "options": { "awslogs-group": Ref(family.logging.family_log_group), "awslogs-stream-prefix": "firelens", "awslogs-create-group": True, }, }, "healthcheck": { "test": [ "CMD-SHELL", 'echo \'{"health": "check"}\' | nc 127.0.0.1 8877 || exit 1', ], "interval": "10s", "retries": 3, "start_period": "15s", "timeout": "5s", }, } if api_health_enabled: config.update( { "healthcheck": { "test": [ "CMD-SHELL", "curl -sq http://127.0.0.1:2020/api/v1/health || exit 1", ], "interval": "15s", "retries": 3, "start_period": "30s", "timeout": "10s", } } ) if enable_prometheus: config["ports"] = [{"target": 2021, "protocol": "tcp"}] return config
[docs]class FluentBit(ManagedSidecar): fluentbit_firelens_defaults: dict = { "Type": "fluentbit", "Options": {"enable-ecs-log-metadata": True}, } def __init__(self, name, definition): super().__init__( name, definition, is_essential=True, image_param=FLUENT_BIT_IMAGE_PARAMETER ) @property def firelens_config(self): return ( getattr(self.container_definition, "FirelensConfiguration") if hasattr(self.container_definition, "FirelensConfiguration") else NoValue ) @firelens_config.setter def firelens_config(self, config): if keyisset("config-file-type", config) and config["config-file-type"] == "s3": config["config-file-type"] = If( USE_FARGATE_CON_T, NoValue, config["config-file-type"] ) config["config-file-value"] = If( USE_FARGATE_CON_T, NoValue, config["config-file-value"] ) parts = re.match( r"arn:aws(-[a-z-]+)?:s3:::(?P<bucket>[a-zA-Z-.\d][^/]+)/(?P<path>[\S]+)$", config["config-file-value"], ) if parts and parts.group("bucket"): from ecs_composex.resource_settings import define_iam_permissions define_iam_permissions( "s3", self.family, self.family.template, "s3ForFirelens", { "RO": { "Action": ["s3:GetObject*", "s3:ListBucket"], "Effect": "Allow", } }, access_definition="RO", resource_arns=[ Sub(f"arn:aws:s3:::{parts.group('bucket')}"), Sub(f"arn:aws:s3:::{parts.group('bucket')}/*"), ], roles=[ self.family.iam_manager.task_role.name, self.family.iam_manager.exec_role.name, ], ) setattr( self.container_definition, "FirelensConfiguration", FirelensConfiguration(**config), )
[docs] def set_firelens_configuration(self): self.firelens_config = self.fluentbit_firelens_defaults
[docs] def add_to_family(self, family: ComposeFamily, is_dependency: bool = False) -> None: """ Adds the container as a sidecar to the family in order to fulfil a specific purpose for an AWS Feature, here, add xray-daemon for dynamic tracing. :param ecs_composex.ecs.ecs_family.ComposeFamily family: :param bool is_dependency: Whether the family services depend on sidecar or not. """ self.family = family family.add_managed_sidecar(self) add_parameters(family.template, [FLUENT_BIT_IMAGE_PARAMETER]) self.image_param = FLUENT_BIT_IMAGE_PARAMETER self.set_as_dependency_to_family_services(is_dependency)
[docs] def set_as_dependency_to_family_services(self, is_dependency: bool = True) -> None: """ As it is the logging router, it needs to start before every other container :param is_dependency: :return: """ for service in chain( self.family.managed_sidecars, self.family.ordered_services ): if service is self: continue if self.name not in service.depends_on: service.depends_on[self.name] = {"condition": "service_healthy"} LOG.info( f"{self.family.name}.{service.name} - Added {self.name} as startup dependency" )