Skip to content

Commit 789dde8

Browse files
committed
feat(controller): add a multi-tenant proxy for prometheus api
1 parent 65631a0 commit 789dde8

8 files changed

Lines changed: 65 additions & 28 deletions

File tree

charts/controller/templates/_helpers.tpl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ env:
112112
valueFrom:
113113
fieldRef:
114114
fieldPath: metadata.namespace
115-
{{- if (.Values.prometheusUrl) }}
116-
- name: "DRYCC_PROMETHEUS_URL"
115+
{{- if (.Values.victoriametricsUrl) }}
116+
- name: "DRYCC_VICTORIAMETRICS_URL"
117117
valueFrom:
118118
secretKeyRef:
119119
name: controller-creds
120-
key: prometheus-url
121-
{{- else if .Values.prometheus.enabled }}
122-
- name: "DRYCC_PROMETHEUS_URL"
123-
value: "http://drycc-victoriametrics-vmselect.{{$.Release.Namespace}}.svc.{{$.Values.global.clusterDomain}}:8481/select/0/prometheus"
120+
key: victoriametrics-url
121+
{{- else if .Values.victoriametrics.enabled }}
122+
- name: "DRYCC_VICTORIAMETRICS_URL"
123+
value: "http://drycc-victoriametrics-vmselect.{{$.Release.Namespace}}.svc.{{$.Values.global.clusterDomain}}:8481"
124124
{{- end }}
125125
{{- if .Values.passport.enabled }}
126126
- name: "DRYCC_PASSPORT_URL"

charts/controller/templates/controller-secret-creds.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ data:
1414
{{- if (.Values.databaseReplicaUrl) }}
1515
database-replica-url: {{ .Values.databaseReplicaUrl | b64enc }}
1616
{{- end }}
17-
{{- if (.Values.prometheusUrl) }}
18-
prometheus-url: {{ .Values.prometheusUrl | b64enc }}
17+
{{- if (.Values.victoriametricsUrl) }}
18+
victoriametrics-url: {{ .Values.victoriametricsUrl | b64enc }}
1919
{{- end }}
2020
{{- if (.Values.passportUrl) }}
2121
passport-url: {{ .Values.passportUrl | b64enc }}

charts/controller/values.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ databaseReplicaUrl: ""
6363
passportUrl: ""
6464
passportKey: ""
6565
passportSecret: ""
66-
# prometheusUrl is will no longer use the built-in prometheus component
67-
prometheusUrl: ""
66+
# victoriametricsUrl is will no longer use the built-in victoriametrics component
67+
victoriametricsUrl: ""
6868
# Workflow-manager Configuration Options
6969
workflowManagerUrl: ""
7070
workflowManagerAccessKey: ""
@@ -169,7 +169,7 @@ registry:
169169
passport:
170170
enabled: true
171171

172-
prometheus:
172+
victoriametrics:
173173
enabled: true
174174

175175
global:

rootfs/api/management/commands/measure_loadbalancers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _measure_loadbalancers(self, app_map, timestamp):
4848
send_measurements.delay(loadbalancers)
4949

5050
def handle(self, *args, **options):
51-
if settings.WORKFLOW_MANAGER_URL and settings.DRYCC_PROMETHEUS_URL:
51+
if settings.WORKFLOW_MANAGER_URL and settings.DRYCC_VICTORIAMETRICS_URL:
5252
timestamp = int(time.time())
5353
task_id = uuid.uuid4().hex
5454
logger.info(f"pushing {task_id} limits to workflow_manager when {timezone.now()}")

rootfs/api/monitor.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def query_prom(url, params) -> list[tuple[dict[str, str], int]]:
5858
async def last_metrics(namespace) -> AsyncGenerator[Iterator, str]:
5959
if not settings.DRYCC_METRICS_CONFIG:
6060
return
61-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query")
61+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")
6262
promql = query_last_metrics_promql_tpl % (
6363
'|'.join(settings.DRYCC_METRICS_CONFIG.keys()),
6464
namespace,
@@ -76,29 +76,29 @@ async def last_metrics(namespace) -> AsyncGenerator[Iterator, str]:
7676

7777
async def query_loadbalancer(namespaces: Iterator[str], start: int, stop: int
7878
) -> list[tuple[dict[str, str], int]]:
79-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query")
79+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")
8080
promql = query_loadbalancer_promql_tpl % "|".join(namespaces)
8181
return await query_prom(url, {"query": promql, "start": start, "end": stop})
8282

8383

8484
async def query_network_receive_flow(namespaces: Iterator[str], start: int, stop: int
8585
) -> list[tuple[dict[str, str], int]]:
86-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query")
86+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")
8787
promql = query_network_receive_flow_promql_tpl % ("|".join(namespaces), f"{stop-start}s")
8888
return await query_prom(url, {"query": promql, "start": start, "end": stop})
8989

9090

9191
async def query_network_transmit_flow(namespaces: Iterator[str], start: int, stop: int
9292
) -> list[tuple[dict[str, str], int]]:
93-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query")
93+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query")
9494
promql = query_network_transmit_flow_promql_tpl % ("|".join(namespaces), f"{stop-start}s")
9595
return await query_prom(url, {"query": promql, "start": start, "end": stop})
9696

9797

9898
async def query_cpu_usage(namespace: str, ptype: str, every: str,
9999
start: int, stop: int, step: int,
100100
) -> list[tuple[dict[str, str], int]]:
101-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query_range")
101+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query_range")
102102
pod_prefix = "%s-%s" % (namespace, ptype)
103103
promql = query_cpu_usage_promql_tpl % (pod_prefix, namespace, every)
104104
return await query_prom(url, {"query": promql, "start": start, "end": stop, "step": step})
@@ -107,7 +107,7 @@ async def query_cpu_usage(namespace: str, ptype: str, every: str,
107107
async def query_memory_usage(namespace: str, ptype: str, every: str,
108108
start: int, stop: int, step: int,
109109
) -> list[tuple[dict[str, str], int]]:
110-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query_range")
110+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query_range")
111111
pod_prefix = "%s-%s" % (namespace, ptype)
112112
promql = query_memory_usage_promql_tpl % (pod_prefix, namespace, every)
113113
return await query_prom(url, {"query": promql, "start": start, "end": stop, "step": step})
@@ -116,7 +116,7 @@ async def query_memory_usage(namespace: str, ptype: str, every: str,
116116
async def query_network_receive_usage(namespace: str, ptype: str, every: str,
117117
start: int, stop: int, step: int,
118118
) -> list[tuple[dict[str, str], int]]:
119-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query_range")
119+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query_range")
120120
pod_prefix = "%s-%s" % (namespace, ptype)
121121
promql = query_network_receive_usage_promql_tpl % (pod_prefix, namespace, every)
122122
return await query_prom(url, {"query": promql, "start": start, "end": stop, "step": step})
@@ -125,7 +125,7 @@ async def query_network_receive_usage(namespace: str, ptype: str, every: str,
125125
async def query_network_transmit_usage(namespace: str, ptype: str, every: str,
126126
start: int, stop: int, step: int,
127127
) -> list[tuple[dict[str, str], int]]:
128-
url = urljoin(settings.DRYCC_PROMETHEUS_URL, "/api/v1/query_range")
128+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, "/select/0/prometheus/api/v1/query_range")
129129
pod_prefix = "%s-%s" % (namespace, ptype)
130130
promql = query_network_transmit_usage_promql_tpl % (pod_prefix, namespace, every)
131131
return await query_prom(url, {"query": promql, "start": start, "end": stop, "step": step})

rootfs/api/settings/production.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@
269269

270270
K8S_API_VERIFY_TLS = os.environ.get('K8S_API_VERIFY_TLS', 'true').lower() == "true"
271271

272-
# drycc prometheus url
273-
DRYCC_PROMETHEUS_URL = os.environ.get('DRYCC_PROMETHEUS_URL', '')
272+
# drycc victoriametrics url
273+
DRYCC_VICTORIAMETRICS_URL = os.environ.get('DRYCC_VICTORIAMETRICS_URL', '')
274274

275275
# drycc metrics config file
276276
DRYCC_METRICS_CONFIG = {}

rootfs/api/urls.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,12 @@
247247
re_path(
248248
r'^nodes/(?P<node>[a-zA-Z0-9-]+)/proxy/metrics(?:/(?P<metrics>[^/]+))?/?$',
249249
views.ProxyMetricsView.as_view()),
250+
# prometheus
251+
re_path(
252+
r'^prometheus/(?P<username>[\w.@+-]+)/(?P<path>.+)/?$', views.ProxyMetricsView.as_view()),
250253
# tokens
251254
re_path(r'^tokens/?$', views.TokenViewSet.as_view({'get': 'list'})),
252255
re_path(r"^tokens/(?P<pk>[-_\w]+)/?$", views.TokenViewSet.as_view({'delete': 'destroy'})),
253-
# social login is placed at the end of the URL match
254-
re_path(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth, name='begin'),
255-
re_path(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete, name='complete'),
256-
re_path('', include('social_django.urls', namespace='social')),
257256
]
258257

259258
mutate_urlpatterns = [
@@ -263,8 +262,15 @@
263262
),
264263
]
265264

265+
# social login is placed at the end of the URL match
266+
social_urlpatterns = [
267+
re_path(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth, name='begin'),
268+
re_path(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete, name='complete'),
269+
re_path('', include('social_django.urls', namespace='social')),
270+
]
271+
266272
# If there is a mutating admission mutate configuration, use mutate url
267273
if settings.MUTATE_KEY:
268274
urlpatterns = mutate_urlpatterns
269275
else:
270-
urlpatterns = app_urlpatterns
276+
urlpatterns = app_urlpatterns + social_urlpatterns

rootfs/api/views.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import random
1111
import aiohttp
1212
import requests
13+
import warnings
1314

15+
from urllib.parse import urljoin
1416
from asgiref.sync import async_to_sync
1517
from django.db.models import Q
1618
from django.core.cache import cache
@@ -38,7 +40,8 @@
3840
from django.views.decorators.cache import never_cache
3941
from django.contrib.auth import REDIRECT_FIELD_NAME
4042
from django.views.decorators.csrf import csrf_exempt
41-
from django.http.response import FileResponse, StreamingHttpResponse
43+
from django.http.response import FileResponse, JsonResponse, StreamingHttpResponse
44+
from channels.db import database_sync_to_async
4245
from social_django.utils import psa
4346
from social_django.views import _do_login
4447
from social_core.utils import setting_name
@@ -1217,6 +1220,8 @@ def wrap(app_id, ptype, every, start, stop, step):
12171220
@method_decorator(cache_page(settings.DRYCC_METRICS_EXPIRY))
12181221
@method_decorator(vary_on_headers("Authorization"))
12191222
def status(self, request, **kwargs):
1223+
warnings.warn(
1224+
'this interface will be removed in the next version.', PendingDeprecationWarning)
12201225
app_id = self._get_app().id
12211226
data = serializers.MetricSerializer(data=self.request.query_params)
12221227
if not data.is_valid():
@@ -1243,6 +1248,8 @@ def status(self, request, **kwargs):
12431248
@method_decorator(cache_page(settings.DRYCC_METRICS_EXPIRY))
12441249
@method_decorator(vary_on_headers("Authorization"))
12451250
def metric(self, request, **kwargs):
1251+
warnings.warn(
1252+
'this interface will be removed in the next version.', PendingDeprecationWarning)
12461253
app_id = self._get_app().id
12471254
return StreamingHttpResponse(
12481255
streaming_content=monitor.last_metrics(app_id)
@@ -1316,3 +1323,27 @@ async def stream_response():
13161323
yield sample
13171324
content_type = f"text/plain; version={__version__}"
13181325
return StreamingHttpResponse(stream_response(), content_type=content_type)
1326+
1327+
1328+
class PrometheusProxy(View):
1329+
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=15)
1330+
authentication = authentication.DryccAuthentication()
1331+
1332+
async def get(self, request, username, path):
1333+
user, _ = await database_sync_to_async(self.authentication.authenticate)(request)
1334+
if not user or user.username != username:
1335+
data, status = {'error': f'unauthorized user: {username}, access denied'}, 403
1336+
else:
1337+
if user.is_superuser or user.is_staff:
1338+
path = f"/select/0/prometheus/{path}"
1339+
else:
1340+
path = f"/select/{user.id}/prometheus/{path}"
1341+
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, path)
1342+
kwargs = {"params": dict(request.GET), "timeout": self.timeout}
1343+
try:
1344+
async with aiohttp.ClientSession() as session:
1345+
async with session.get(url, **kwargs) as response:
1346+
data, status = await response.json(), response.status
1347+
except aiohttp.ClientError as e:
1348+
data, status = {'error': f'victoriametrics connection failed: {str(e)}'}, 502
1349+
return JsonResponse(data, status=status)

0 commit comments

Comments
 (0)