summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorПавел Жуков <33721692+LeaveMyYard@users.noreply.github.com>2023-04-13 11:37:32 +0300
committerПавел Жуков <33721692+LeaveMyYard@users.noreply.github.com>2023-04-13 11:37:32 +0300
commitb0222c746d20c0352ab37cfe6527197afaab78a4 (patch)
tree8d01dd35e74eb5eef0380cd59a8c2572a2fc10e7
parent86e05a612ed90e76693ac1159120d7c0b3537e41 (diff)
Make KRR work from inside the cluster
-rw-r--r--.dockerignore17
-rw-r--r--Dockerfile30
-rw-r--r--pyproject.toml2
-rw-r--r--robusta_krr/core/integrations/kubernetes.py24
-rw-r--r--robusta_krr/core/integrations/prometheus.py2
-rw-r--r--robusta_krr/core/models/config.py16
-rw-r--r--robusta_krr/core/models/objects.py2
-rw-r--r--robusta_krr/core/runner.py2
-rw-r--r--robusta_krr/utils/service_discovery.py1
9 files changed, 79 insertions, 17 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..240e615
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,17 @@
+# .dockerignore
+__pycache__
+*.pyc
+*.pyo
+*.pyd
+
+# Exclude development files
+.git
+.gitignore
+Dockerfile
+*.md
+*.txt
+.vscode
+
+# Exclude logs and cache
+logs/
+cache/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..56a182f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,30 @@
+# Use the official Python 3.9 slim image as the base image
+FROM python:3.9-slim as builder
+
+# Set the working directory
+WORKDIR /app
+
+# Install system dependencies required for Poetry
+RUN apt-get update && \
+ apt-get install --no-install-recommends -y curl && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+# Install Poetry
+RUN curl -sSL https://install.python-poetry.org | python -
+
+# Add Poetry to the PATH
+ENV PATH="/root/.local/bin:${PATH}"
+
+# Copy the pyproject.toml files
+COPY pyproject.toml ./
+
+# Install the project dependencies
+RUN poetry config virtualenvs.create false \
+ && poetry install --no-dev --no-interaction --no-ansi --no-root
+
+# Copy the rest of the application code
+COPY . .
+
+# Run the application using 'poetry run krr simple'
+CMD ["python", "krr.py", "simple"]
diff --git a/pyproject.toml b/pyproject.toml
index 7e9eba6..44b6490 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ include_trailing_comma = true
krr = "robusta_krr.main:run"
[tool.poetry.dependencies]
-python = ">=3.9"
+python = ">=3.9,<3.12"
typer = {extras = ["all"], version = "^0.7.0"}
pydantic = "1.10.7"
kubernetes = "^26.1.0"
diff --git a/robusta_krr/core/integrations/kubernetes.py b/robusta_krr/core/integrations/kubernetes.py
index 3affe7e..c4a3fc8 100644
--- a/robusta_krr/core/integrations/kubernetes.py
+++ b/robusta_krr/core/integrations/kubernetes.py
@@ -1,6 +1,6 @@
import asyncio
import itertools
-from typing import Union
+from typing import Optional, Union
from kubernetes import client, config # type: ignore
from kubernetes.client.models import (
@@ -22,11 +22,11 @@ from robusta_krr.utils.configurable import Configurable
class ClusterLoader(Configurable):
- def __init__(self, cluster: str, *args, **kwargs):
+ def __init__(self, cluster: Optional[str], *args, **kwargs):
super().__init__(*args, **kwargs)
self.cluster = cluster
- self.api_client = config.new_client_from_config(context=cluster)
+ self.api_client = config.new_client_from_config(context=cluster) if cluster is not None else None
self.apps = client.AppsV1Api(api_client=self.api_client)
self.batch = client.BatchV1Api(api_client=self.api_client)
self.core = client.CoreV1Api(api_client=self.api_client)
@@ -168,17 +168,17 @@ class ClusterLoader(Configurable):
class KubernetesLoader(Configurable):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- config.load_kube_config()
-
- async def list_clusters(self) -> list[str]:
+ async def list_clusters(self) -> Optional[list[str]]:
"""List all clusters.
Returns:
A list of clusters.
"""
+ if self.config.inside_cluster:
+ self.debug("Working inside the cluster")
+ return None
+
contexts, current_context = await asyncio.to_thread(config.list_kube_config_contexts)
self.debug(f"Found {len(contexts)} clusters: {', '.join([context['name'] for context in contexts])}")
@@ -196,13 +196,17 @@ class KubernetesLoader(Configurable):
return [context["name"] for context in contexts if context["name"] in self.config.clusters]
- async def list_scannable_objects(self, clusters: list[str]) -> list[K8sObjectData]:
+ async def list_scannable_objects(self, clusters: Optional[list[str]]) -> list[K8sObjectData]:
"""List all scannable objects.
Returns:
A list of scannable objects.
"""
- cluster_loaders = [ClusterLoader(cluster=cluster, config=self.config) for cluster in clusters]
+ if clusters is None:
+ cluster_loaders = [ClusterLoader(cluster=None, config=self.config)]
+ else:
+ cluster_loaders = [ClusterLoader(cluster=cluster, config=self.config) for cluster in clusters]
+
objects = await asyncio.gather(*[cluster_loader.list_scannable_objects() for cluster_loader in cluster_loaders])
return list(itertools.chain(*objects))
diff --git a/robusta_krr/core/integrations/prometheus.py b/robusta_krr/core/integrations/prometheus.py
index a52dde7..39c7f00 100644
--- a/robusta_krr/core/integrations/prometheus.py
+++ b/robusta_krr/core/integrations/prometheus.py
@@ -67,7 +67,7 @@ class PrometheusLoader(Configurable):
self.auth_header = self.config.prometheus_auth_header
self.ssl_enabled = self.config.prometheus_ssl_enabled
- self.api_client = k8s_config.new_client_from_config(context=cluster)
+ self.api_client = k8s_config.new_client_from_config(context=cluster) if cluster is not None else None
self.prometheus_discovery = PrometheusDiscovery(config=self.config)
self.url = self.config.prometheus_url
diff --git a/robusta_krr/core/models/config.py b/robusta_krr/core/models/config.py
index 153f9d3..96c4e61 100644
--- a/robusta_krr/core/models/config.py
+++ b/robusta_krr/core/models/config.py
@@ -1,10 +1,19 @@
from typing import Literal, Optional, Union
import pydantic as pd
+from kubernetes import config
+from kubernetes.config.config_exception import ConfigException
from robusta_krr.core.abstract.formatters import BaseFormatter
from robusta_krr.core.abstract.strategies import AnyStrategy, BaseStrategy
+try:
+ config.load_incluster_config()
+ IN_CLUSTER = True
+except ConfigException:
+ config.load_kube_config()
+ IN_CLUSTER = False
+
class Config(pd.BaseSettings):
quiet: bool = pd.Field(False)
@@ -13,9 +22,6 @@ class Config(pd.BaseSettings):
clusters: Union[list[str], Literal["*"], None] = None
namespaces: Union[list[str], Literal["*"]] = pd.Field("*")
- # Make this True if you are running KRR inside the cluster
- inside_cluster: bool = pd.Field(False)
-
# Value settings
cpu_min_value: int = pd.Field(5, ge=0) # in millicores
memory_min_value: int = pd.Field(10, ge=0) # in megabytes
@@ -63,3 +69,7 @@ class Config(pd.BaseSettings):
def validate_format(cls, v: str) -> str:
BaseFormatter.find(v) # NOTE: raises if strategy is not found
return v
+
+ @property
+ def inside_cluster(self) -> bool:
+ return IN_CLUSTER
diff --git a/robusta_krr/core/models/objects.py b/robusta_krr/core/models/objects.py
index 74f07ac..8c8acfd 100644
--- a/robusta_krr/core/models/objects.py
+++ b/robusta_krr/core/models/objects.py
@@ -6,7 +6,7 @@ from robusta_krr.core.models.allocations import ResourceAllocations
class K8sObjectData(pd.BaseModel):
- cluster: str
+ cluster: Optional[str]
name: str
container: str
pods: list[str]
diff --git a/robusta_krr/core/runner.py b/robusta_krr/core/runner.py
index 7aae3bd..51f4f05 100644
--- a/robusta_krr/core/runner.py
+++ b/robusta_krr/core/runner.py
@@ -121,7 +121,7 @@ class Runner(Configurable):
async def _collect_result(self) -> Result:
clusters = await self._k8s_loader.list_clusters()
- self.debug(f'Using clusters: {", ".join(clusters)}')
+ self.debug(f'Using clusters: {clusters if clusters is not None else "inner cluster"}')
objects = await self._k8s_loader.list_scannable_objects(clusters)
resource_recommendations = await self._gather_objects_recommendations(objects)
diff --git a/robusta_krr/utils/service_discovery.py b/robusta_krr/utils/service_discovery.py
index f341225..a6bc8b9 100644
--- a/robusta_krr/utils/service_discovery.py
+++ b/robusta_krr/utils/service_discovery.py
@@ -6,6 +6,7 @@ from kubernetes import client
from kubernetes.client import V1ServiceList
from kubernetes.client.api_client import ApiClient
from kubernetes.client.models.v1_service import V1Service
+from kubernetes.config.config_exception import ConfigException
from robusta_krr.utils.configurable import Configurable