-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Moved the logic from #93 for a new refined structure
Co-authored-by: Megrez Lu <[email protected]>
- Loading branch information
1 parent
4fedd82
commit c7ad1cd
Showing
12 changed files
with
309 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 0 additions & 10 deletions
10
robusta_krr/core/integrations/kubernetes/workload_loader/prometheus.py
This file was deleted.
Oops, something went wrong.
31 changes: 31 additions & 0 deletions
31
robusta_krr/core/integrations/kubernetes/workload_loader/prometheus/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import asyncio | ||
import itertools | ||
import logging | ||
|
||
|
||
from robusta_krr.core.integrations.prometheus.loader import PrometheusMetricsLoader | ||
from robusta_krr.core.models.config import settings | ||
from robusta_krr.core.integrations.prometheus.metrics_service.prometheus_metrics_service import PrometheusMetricsService | ||
from robusta_krr.core.models.objects import K8sWorkload, PodData | ||
from ..base import BaseWorkloadLoader | ||
from .loaders import BaseKindLoader, DeploymentLoader | ||
|
||
|
||
logger = logging.getLogger("krr") | ||
|
||
|
||
class PrometheusWorkloadLoader(BaseWorkloadLoader): | ||
workloads: list[type[BaseKindLoader]] = [DeploymentLoader] | ||
|
||
def __init__(self, cluster: str, metric_loader: PrometheusMetricsLoader) -> None: | ||
self.cluster = cluster | ||
self.metric_service = metric_loader | ||
self.loaders = [loader(metric_loader) for loader in self.workloads] | ||
|
||
async def list_workloads(self) -> list[K8sWorkload]: | ||
return itertools.chain(await asyncio.gather(*[loader.list_workloads(settings.namespaces, "") for loader in self.loaders])) | ||
|
||
async def list_pods(self, object: K8sWorkload) -> list[PodData]: | ||
# This should not be implemented, as implementation will repeat PrometheusMetricsLoader.load_pods | ||
# As this method is ment to be a fallback, repeating the same logic will not be beneficial | ||
raise NotImplementedError |
19 changes: 19 additions & 0 deletions
19
robusta_krr/core/integrations/kubernetes/workload_loader/prometheus/loaders/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from .base import BaseKindLoader | ||
from .cronjobs import CronJobLoader | ||
from .daemonsets import DaemonSetLoader | ||
from .deploymentconfigs import DeploymentConfigLoader | ||
from .deployments import DeploymentLoader | ||
from .jobs import JobLoader | ||
from .rollouts import RolloutLoader | ||
from .statefulsets import StatefulSetLoader | ||
|
||
__all__ = [ | ||
"BaseKindLoader", | ||
"CronJobLoader", | ||
"DeploymentLoader", | ||
"DaemonSetLoader", | ||
"DeploymentConfigLoader", | ||
"JobLoader", | ||
"RolloutLoader", | ||
"StatefulSetLoader", | ||
] |
126 changes: 126 additions & 0 deletions
126
robusta_krr/core/integrations/kubernetes/workload_loader/prometheus/loaders/base.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import abc | ||
import asyncio | ||
import logging | ||
from concurrent.futures import ThreadPoolExecutor | ||
from typing import Any, Iterable, Literal, Optional, Union | ||
|
||
from kubernetes import client # type: ignore | ||
from kubernetes.client.api_client import ApiClient # type: ignore | ||
from kubernetes.client.models import ( # type: ignore | ||
V1Container, | ||
V1DaemonSet, | ||
V1Deployment, | ||
V1Job, | ||
V1Pod, | ||
V1PodList, | ||
V1StatefulSet, | ||
) | ||
|
||
from robusta_krr.core.integrations.prometheus.loader import PrometheusMetricsLoader | ||
from robusta_krr.core.models.allocations import RecommendationValue, ResourceAllocations, ResourceType | ||
from robusta_krr.core.models.objects import K8sWorkload, KindLiteral, PodData | ||
|
||
logger = logging.getLogger("krr") | ||
|
||
AnyKubernetesAPIObject = Union[V1Deployment, V1DaemonSet, V1StatefulSet, V1Pod, V1Job] | ||
HPAKey = tuple[str, str, str] | ||
|
||
|
||
class BaseKindLoader(abc.ABC): | ||
""" | ||
This class is used to define how to load a specific kind of Kubernetes object. | ||
It does not load the objects itself, but is used by the `KubeAPIWorkloadLoader` to load objects. | ||
""" | ||
|
||
kind: KindLiteral | ||
|
||
def __init__(self, metrics_loader: PrometheusMetricsLoader) -> None: | ||
self.metrics_loader = metrics_loader | ||
|
||
@abc.abstractmethod | ||
def list_workloads(self, namespaces: Union[list[str], Literal["*"]], label_selector: str) -> list[K8sWorkload]: | ||
pass | ||
|
||
async def __parse_allocation(self, namespace: str, pod_selector: str, container_name: str) -> ResourceAllocations: | ||
limits = await self.metrics_loader.loader.query( | ||
"avg by(resource) (kube_pod_container_resource_limits{" | ||
f'namespace="{namespace}", ' | ||
f'pod=~"{pod_selector}", ' | ||
f'container="{container_name}"' | ||
"})" | ||
) | ||
requests = await self.metrics_loader.loader.query( | ||
"avg by(resource) (kube_pod_container_resource_requests{" | ||
f'namespace="{namespace}", ' | ||
f'pod=~"{pod_selector}", ' | ||
f'container="{container_name}"' | ||
"})" | ||
) | ||
requests_values: dict[ResourceType, RecommendationValue] = {ResourceType.CPU: None, ResourceType.Memory: None} | ||
limits_values: dict[ResourceType, RecommendationValue] = {ResourceType.CPU: None, ResourceType.Memory: None} | ||
for limit in limits: | ||
if limit["metric"]["resource"] == ResourceType.CPU: | ||
limits_values[ResourceType.CPU] = float(limit["value"][1]) | ||
elif limit["metric"]["resource"] == ResourceType.Memory: | ||
limits_values[ResourceType.Memory] = float(limit["value"][1]) | ||
|
||
for request in requests: | ||
if request["metric"]["resource"] == ResourceType.CPU: | ||
requests_values[ResourceType.CPU] = float(request["value"][1]) | ||
elif request["metric"]["resource"] == ResourceType.Memory: | ||
requests_values[ResourceType.Memory] = float(request["value"][1]) | ||
return ResourceAllocations(requests=requests_values, limits=limits_values) | ||
|
||
async def __build_from_owner( | ||
self, namespace: str, app_name: str, containers: list[str], pod_names: list[str] | ||
) -> list[K8sWorkload]: | ||
return [ | ||
K8sWorkload( | ||
cluster=None, | ||
namespace=namespace, | ||
name=app_name, | ||
kind="Deployment", | ||
container=container_name, | ||
allocations=await self.__parse_allocation(namespace, "|".join(pod_names), container_name), # find | ||
pods=[PodData(name=pod_name, deleted=False) for pod_name in pod_names], # list pods | ||
) | ||
for container_name in containers | ||
] | ||
|
||
async def _list_containers(self, namespace: str, pod_selector: str) -> list[str]: | ||
containers = await self.metrics_loader.loader.query( | ||
f""" | ||
count by (container) ( | ||
kube_pod_container_info{{ | ||
namespace="{namespace}", | ||
pod=~"{pod_selector}" | ||
}} | ||
) | ||
""" | ||
) | ||
return [container["metric"]["container"] for container in containers] | ||
|
||
async def _list_containers_in_pods( | ||
self, app_name: str, pod_owner_kind: str, namespace: str, owner_name: str | ||
) -> list[K8sWorkload]: | ||
if pod_owner_kind == "ReplicaSet": | ||
# owner_name is ReplicaSet names | ||
pods = await self.metrics_loader.loader.query( | ||
f""" | ||
count by (owner_name, replicaset, pod) ( | ||
kube_pod_owner{{ | ||
namespace="{namespace}", | ||
owner_name=~"{owner_name}", ' | ||
owner_kind="ReplicaSet" | ||
}} | ||
) | ||
""" | ||
) | ||
if pods is None or len(pods) == 0: | ||
return [] # no container | ||
# [{'metric': {'owner_name': 'wbjs-algorithm-base-565b645489', 'pod': 'wbjs-algorithm-base-565b645489-jqt4x'}, 'value': [1685529217, '1']}, | ||
# {'metric': {'owner_name': 'wbjs-algorithm-base-565b645489', 'pod': 'wbjs-algorithm-base-565b645489-lj9qg'}, 'value': [1685529217, '1']}] | ||
pod_names = [pod["metric"]["pod"] for pod in pods] | ||
container_names = await self._list_containers(namespace, "|".join(pod_names)) | ||
return await self.__build_from_owner(namespace, app_name, container_names, pod_names) | ||
return [] |
Oops, something went wrong.