From 9e554912c3ab0dc51d4116fae9112523a9a042d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20G=C3=A9czi?= Date: Fri, 17 Nov 2023 12:00:00 +0000 Subject: [PATCH] feat(agent): Add basic support for EKS Pods on Fargate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferenc Géczi --- instana/agent/aws_eks_fargate.py | 93 +++++++++++++++++++++++ instana/collector/aws_eks_fargate.py | 62 +++++++++++++++ instana/collector/helpers/eks/__init__.py | 0 instana/collector/helpers/eks/pod.py | 14 ++++ instana/options.py | 4 + instana/singletons.py | 7 ++ 6 files changed, 180 insertions(+) create mode 100644 instana/agent/aws_eks_fargate.py create mode 100644 instana/collector/aws_eks_fargate.py create mode 100644 instana/collector/helpers/eks/__init__.py create mode 100644 instana/collector/helpers/eks/pod.py diff --git a/instana/agent/aws_eks_fargate.py b/instana/agent/aws_eks_fargate.py new file mode 100644 index 000000000..bc29f594f --- /dev/null +++ b/instana/agent/aws_eks_fargate.py @@ -0,0 +1,93 @@ +# (c) Copyright IBM Corp. 2023 + +""" +The Instana agent (for AWS EKS Fargate) that manages +monitoring state and reporting that data. +""" +import os +import time +from instana.options import EKSFargateOptions +from instana.collector.aws_eks_fargate import EKSFargateCollector +from instana.collector.helpers.eks.pod import get_pod_name +from instana.log import logger +from instana.util import to_json +from instana.agent.base import BaseAgent +from instana.version import VERSION + + +class EKSFargateAgent(BaseAgent): + """ In-process agent for AWS Fargate """ + def __init__(self): + super(EKSFargateAgent, self).__init__() + + self.options = EKSFargateOptions() + self.collector = None + self.report_headers = None + self._can_send = False + self.podname = get_pod_name() + + # Update log level (if INSTANA_LOG_LEVEL was set) + self.update_log_level() + + logger.info("Stan is on the EKS Pod on AWS Fargate scene. Starting Instana instrumentation version: %s", VERSION) + + if self._validate_options(): + self._can_send = True + self.collector = EKSFargateCollector(self) + self.collector.start() + else: + logger.warning("Required INSTANA_AGENT_KEY and/or INSTANA_ENDPOINT_URL environment variables not set. " + "We will not be able to monitor this Pod.") + + def can_send(self): + """ + Are we in a state where we can send data? + @return: Boolean + """ + return self._can_send + + def get_from_structure(self): + """ + Retrieves the From data that is reported alongside monitoring data. + @return: dict() + """ + + return {'hl': True, 'cp': 'k8s', 'e': self.podname} + + def report_data_payload(self, payload): + """ + Used to report metrics and span data to the endpoint URL in self.options.endpoint_url + """ + response = None + try: + if self.report_headers is None: + # Prepare request headers + self.report_headers = dict() + self.report_headers["Content-Type"] = "application/json" + self.report_headers["X-Instana-Host"] = self.podname + self.report_headers["X-Instana-Key"] = self.options.agent_key + + response = self.client.post(self.__data_bundle_url(), + data=to_json(payload), + headers=self.report_headers, + timeout=self.options.timeout, + verify=self.options.ssl_verify, + proxies=self.options.endpoint_proxy) + + if not 200 <= response.status_code < 300: + logger.info("report_data_payload: Instana responded with status code %s", response.status_code) + except Exception as exc: + logger.debug("report_data_payload: connection error (%s)", type(exc)) + return response + + def _validate_options(self): + """ + Validate that the options used by this Agent are valid. e.g. can we report data? + """ + return self.options.endpoint_url is not None and self.options.agent_key is not None + + def __data_bundle_url(self): + """ + URL for posting metrics to the host agent. Only valid when announced. + """ + return "%s/bundle" % self.options.endpoint_url diff --git a/instana/collector/aws_eks_fargate.py b/instana/collector/aws_eks_fargate.py new file mode 100644 index 000000000..ff154a9aa --- /dev/null +++ b/instana/collector/aws_eks_fargate.py @@ -0,0 +1,62 @@ +# (c) Copyright IBM Corp. 2023 + +""" +Collector for EKS Pods on AWS Fargate: Manages the periodic collection of metrics & snapshot data +""" + +from time import time +from instana.log import logger +from instana.collector.base import BaseCollector +from instana.util import DictionaryOfStan + + +class EKSFargateCollector(BaseCollector): + """ Collector for EKS Pods on AWS Fargate """ + + def __init__(self, agent): + super(EKSFargateCollector, self).__init__(agent) + logger.debug("Loading Collector for EKS Pods on AWS Fargate ") + + self.snapshot_data = DictionaryOfStan() + self.snapshot_data_sent = False + self.podname = agent.podname + + def should_send_snapshot_data(self): + return int(time()) - self.snapshot_data_last_sent > self.snapshot_data_interval + + def collect_snapshot(self, event, context): + self.context = context + self.event = event + + try: + plugin_data = dict() + plugin_data["name"] = "com.instana.plugin.aws.eks" + plugin_data["entityId"] = self.self.podname + self.snapshot_data["plugins"] = [plugin_data] + except Exception: + logger.debug("collect_snapshot error", exc_info=True) + return self.snapshot_data + + def prepare_payload(self): + payload = DictionaryOfStan() + payload["spans"] = [] + payload["metrics"]["plugins"] = [] + + try: + if not self.span_queue.empty(): + payload["spans"] = self.queued_spans() + + with_snapshot = self.should_send_snapshot_data() + + plugins = [] + for helper in self.helpers: + plugins.extend(helper.collect_metrics(with_snapshot=with_snapshot)) + + payload["metrics"]["plugins"] = plugins + + if with_snapshot is True: + self.snapshot_data_last_sent = int(time()) + except Exception: + logger.debug("collect_snapshot error", exc_info=True) + + return payload diff --git a/instana/collector/helpers/eks/__init__.py b/instana/collector/helpers/eks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/instana/collector/helpers/eks/pod.py b/instana/collector/helpers/eks/pod.py new file mode 100644 index 000000000..092b0e0c6 --- /dev/null +++ b/instana/collector/helpers/eks/pod.py @@ -0,0 +1,14 @@ +# (c) Copyright IBM Corp. 2023 + +""" Module to handle the collection of container metrics for EKS Pods on AWS Fargate """ +import os +import re +from instana.log import logger + + +def get_pod_name(): + podname = os.environ.get('HOSTNAME') + + if not podname: + logger.warning("Failed to determine podname from EKS hostname.") + return podname diff --git a/instana/options.py b/instana/options.py index bf0b6be5f..9182eb2d4 100644 --- a/instana/options.py +++ b/instana/options.py @@ -155,6 +155,10 @@ def __init__(self, **kwds): self.zone = os.environ.get("INSTANA_ZONE", None) +class EKSFargateOptions(AWSFargateOptions): + """ Options class for EKS Pods on AWS Fargate. Holds settings specific to EKS Pods on AWS Fargate. """ + def __init__(self, **kwds): + super(EKSFargateOptions, self).__init__() class GCROptions(ServerlessOptions): """ Options class for Google Cloud Run. Holds settings specific to Google Cloud Run. """ diff --git a/instana/singletons.py b/instana/singletons.py index f59fcda32..9f6e625ed 100644 --- a/instana/singletons.py +++ b/instana/singletons.py @@ -19,6 +19,7 @@ aws_env = os.environ.get("AWS_EXECUTION_ENV", "") env_is_test = "INSTANA_TEST" in os.environ env_is_aws_fargate = aws_env == "AWS_ECS_FARGATE" +env_is_aws_eks_fargate = (os.environ.get("INSTANA_TRACER_ENVIRONMENT") == "AWS_EKS_FARGATE") env_is_aws_lambda = "AWS_Lambda_" in aws_env k_service = os.environ.get("K_SERVICE") k_configuration = os.environ.get("K_CONFIGURATION") @@ -53,6 +54,12 @@ agent = GCRAgent(service=k_service, configuration=k_configuration, revision=k_revision) span_recorder = StanRecorder(agent) +elif env_is_aws_eks_fargate: + from .agent.aws_eks_fargate import EKSFargateAgent + from .recorder import StanRecorder + + agent = EKSFargateAgent() + span_recorder = StanRecorder(agent) else: from .agent.host import HostAgent from .recorder import StanRecorder