Skip to content

Commit f88e575

Browse files
authored
feat: bklog 支持多租户配置 (#2772)
1 parent 4b530bf commit f88e575

10 files changed

Lines changed: 347 additions & 44 deletions

File tree

apiserver/paasng/conf.yaml.tpl

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -696,17 +696,6 @@ BK_CI_CLIENT_USERNAME = "blueking"
696696
# ENABLE_BK_LOG_APIGW: True
697697
# 蓝鲸日志平台网关的环境,仅在 ENABLE_BK_LOG_APIGW=True 时生效
698698
# BK_LOG_APIGW_SERVICE_STAGE: stag
699-
# 日志保存时间(天数),默认值 14
700-
# BKLOG_RETENTION: 14
701-
# Elasticsearch 索引分片数,默认值 1
702-
# BKLOG_ES_SHARDS: 1
703-
# 存储副本数,默认值 1
704-
# BKLOG_STORAGE_REPLICAS: 1
705-
# 蓝鲸日志平台相关的配置项 (当设置此项时,上述单独的 BKLOG_* 配置项将被覆盖)
706-
# BKLOG_CONFIG:
707-
# RETENTION: 14
708-
# ES_SHARDS: 1
709-
# STORAGE_REPLICAS: 1
710699

711700

712701
## ------------------------------------ 蓝鲸通知中心配置 ------------------------------------

apiserver/paasng/locale/en/LC_MESSAGES/django.po

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,26 @@ msgstr "Is Built-in Collection Item"
705705
msgid "是否启用"
706706
msgstr "Is Enabled"
707707

708+
#: paasng/accessories/log/models.py:208
709+
msgid "日志平台存储集群 ID"
710+
msgstr "Blueking log platform Storage Cluster ID"
711+
712+
#: paasng/accessories/log/models.py:209
713+
msgid "日志保存时间(天)"
714+
msgstr "Log Retention Period (Days)"
715+
716+
#: paasng/accessories/log/models.py:210
717+
msgid "ES 索引分片数"
718+
msgstr "Number of ES Index Shards"
719+
720+
#: paasng/accessories/log/models.py:211
721+
msgid "存储副本数"
722+
msgstr "Number of Storage Replicas"
723+
724+
#: paasng/accessories/log/models.py:214
725+
msgid "时区"
726+
msgstr "Time Zone"
727+
708728
#: paasng/accessories/log/serializers.py:156
709729
#, python-brace-format
710730
msgid "最多仅能查看前 {MAX_RESULT_WINDOW} 条日志"

apiserver/paasng/paasng/accessories/log/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,11 @@ class BkLogApiError(BkLogGatewayServiceError):
5454
"""When calling the bk_log api, bk_log returns an error message,
5555
which needs to be captured and displayed to the user on the page
5656
"""
57+
58+
59+
class TenantLogConfigNotFoundError(Exception):
60+
"""该租户日志配置不存在"""
61+
62+
def __init__(self, tenant_id: str):
63+
self.message = f"TenantLogConfig not found for tenant_id: {tenant_id}"
64+
super().__init__(self.message)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# -*- coding: utf-8 -*-
2+
# TencentBlueKing is pleased to support the open source community by making
3+
# 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available.
4+
# Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
5+
# Licensed under the MIT License (the "License"); you may not use this file except
6+
# in compliance with the License. You may obtain a copy of the License at
7+
#
8+
# http://opensource.org/licenses/MIT
9+
#
10+
# Unless required by applicable law or agreed to in writing, software distributed under
11+
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12+
# either express or implied. See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# We undertake not to change the open source license (MIT license) applicable
16+
# to the current version of the project delivered to anyone in the future.
17+
18+
from django.core.exceptions import ValidationError
19+
from django.core.management.base import BaseCommand, CommandError
20+
21+
from paasng.accessories.log.models import TenantLogConfig
22+
from paasng.core.tenant.user import get_init_tenant_id
23+
24+
25+
class Command(BaseCommand):
26+
help = "Create TenantLogConfig for a tenant"
27+
config_fields = (
28+
"storage_cluster_id",
29+
"retention",
30+
"es_shards",
31+
"storage_replicas",
32+
"time_zone",
33+
)
34+
35+
def add_arguments(self, parser):
36+
tenant_group = parser.add_mutually_exclusive_group(required=True)
37+
tenant_group.add_argument("--tenant-id", help="tenant id")
38+
tenant_group.add_argument(
39+
"--default-tenant",
40+
action="store_true",
41+
default=False,
42+
help="为默认租户创建配置,不可与 --tenant-id 同时指定",
43+
)
44+
parser.add_argument(
45+
"--update",
46+
action="store_true",
47+
default=False,
48+
help="允许更新相同 tenant_id 已存在的配置",
49+
)
50+
parser.add_argument("--storage-cluster-id", required=True, type=int, help="日志平台存储集群 ID")
51+
parser.add_argument("--retention", type=int, default=14, help="日志保存时间(天),默认 14")
52+
parser.add_argument("--es-shards", type=int, default=1, help="ES 索引分片数,默认 1")
53+
parser.add_argument("--storage-replicas", type=int, default=1, help="存储副本数,默认 1")
54+
parser.add_argument("--time-zone", type=int, default=8, help="时区,如 8 表示 UTC+8,默认 8")
55+
56+
def handle(self, *args, **options):
57+
tenant_id = get_init_tenant_id() if options["default_tenant"] else options["tenant_id"]
58+
config_data = {field: options[field] for field in self.config_fields}
59+
allow_update = options["update"]
60+
existing = TenantLogConfig.objects.filter(tenant_id=tenant_id).first()
61+
62+
if existing and not allow_update:
63+
raise CommandError(f"TenantLogConfig already exists for tenant_id={tenant_id}, use --update to overwrite")
64+
65+
try:
66+
config = existing or TenantLogConfig(tenant_id=tenant_id)
67+
for field, value in config_data.items():
68+
setattr(config, field, value)
69+
action = "Updated" if existing else "Created"
70+
71+
config.full_clean()
72+
if existing:
73+
config.save(update_fields=[*config_data.keys(), "updated"])
74+
else:
75+
config.save()
76+
except ValidationError as e:
77+
raise CommandError(f"Invalid TenantLogConfig data: {e}")
78+
79+
self.stdout.write(
80+
self.style.SUCCESS(
81+
f"{action} TenantLogConfig for tenant_id={tenant_id}: "
82+
f"storage_cluster_id={config.storage_cluster_id}, retention={config.retention}, "
83+
f"es_shards={config.es_shards}, storage_replicas={config.storage_replicas}, "
84+
f"time_zone={config.time_zone}"
85+
)
86+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generated by Django 4.2.23 on 2026-04-07 08:28
2+
3+
from django.db import migrations, models
4+
import uuid
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('log', '0007_customcollectorconfig_tenant_id_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='TenantLogConfig',
16+
fields=[
17+
('uuid', models.UUIDField(auto_created=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='UUID')),
18+
('created', models.DateTimeField(auto_now_add=True)),
19+
('updated', models.DateTimeField(auto_now=True)),
20+
('tenant_id', models.CharField(default='default', help_text='本条数据的所属租户', max_length=32, unique=True, verbose_name='租户 ID')),
21+
('storage_cluster_id', models.IntegerField(help_text='指定该租户日志存储的 ES 集群', verbose_name='日志平台存储集群 ID')),
22+
('retention', models.IntegerField(verbose_name='日志保存时间(天)')),
23+
('es_shards', models.IntegerField(verbose_name='ES 索引分片数')),
24+
('storage_replicas', models.IntegerField(verbose_name='存储副本数')),
25+
('time_zone', models.IntegerField(help_text='日志时间字段解析时区,如 8 代表 UTC+8', verbose_name='时区')),
26+
],
27+
options={
28+
'abstract': False,
29+
},
30+
),
31+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# -*- coding: utf-8 -*-
2+
# TencentBlueKing is pleased to support the open source community by making
3+
# 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available.
4+
# Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
5+
# Licensed under the MIT License (the "License"); you may not use this file except
6+
# in compliance with the License. You may obtain a copy of the License at
7+
#
8+
# http://opensource.org/licenses/MIT
9+
#
10+
# Unless required by applicable law or agreed to in writing, software distributed under
11+
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12+
# either express or implied. See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# We undertake not to change the open source license (MIT license) applicable
16+
# to the current version of the project delivered to anyone in the future.
17+
18+
# 历史兼容 migration, 从已被废弃的 BKLOG_CONFIG 配置项, 初始化为默认租户的 TenantLogConfig 记录
19+
20+
import datetime
21+
22+
from paasng.settings import settings
23+
from django.db import migrations
24+
from django.utils.timezone import get_default_timezone
25+
from paasng.core.tenant.user import get_init_tenant_id
26+
27+
BKLOG_TIME_ZONE = settings.get("BKLOG_TIME_ZONE")
28+
## 日志平台存储集群 ID
29+
BKLOG_STORAGE_CLUSTER_ID = settings.get("BKLOG_STORAGE_CLUSTER_ID")
30+
## 日志保存时间(天数),默认值 14
31+
BKLOG_RETENTION = int(settings.get("BKLOG_RETENTION", 14))
32+
## Elasticsearch 索引分片数,默认值 1
33+
BKLOG_ES_SHARDS = int(settings.get("BKLOG_ES_SHARDS", 1))
34+
## 存储副本数,默认值 1
35+
BKLOG_STORAGE_REPLICAS = int(settings.get("BKLOG_STORAGE_REPLICAS", 1))
36+
BKLOG_CONFIG = settings.get(
37+
"BKLOG_CONFIG",
38+
{
39+
"TIME_ZONE": BKLOG_TIME_ZONE,
40+
"STORAGE_CLUSTER_ID": BKLOG_STORAGE_CLUSTER_ID,
41+
"RETENTION": BKLOG_RETENTION,
42+
"ES_SHARDS": BKLOG_ES_SHARDS,
43+
"STORAGE_REPLICAS": BKLOG_STORAGE_REPLICAS,
44+
},
45+
)
46+
47+
48+
def get_time_zone() -> int:
49+
# 历史兼容,默认使用 django 的时区设置
50+
time_zone = BKLOG_CONFIG.get("TIME_ZONE")
51+
if time_zone is not None:
52+
return time_zone
53+
54+
tz = get_default_timezone()
55+
return int(tz.utcoffset(datetime.datetime.now()).total_seconds() // 60 // 60)
56+
57+
58+
def init_tenant_log_config(apps, schema_editor):
59+
tenant_log_config = apps.get_model("log", "TenantLogConfig")
60+
storage_cluster_id = BKLOG_CONFIG.get("STORAGE_CLUSTER_ID")
61+
62+
if storage_cluster_id is None:
63+
return
64+
65+
tenant_log_config.objects.get_or_create(
66+
tenant_id=get_init_tenant_id(),
67+
defaults={
68+
"storage_cluster_id": storage_cluster_id,
69+
"retention": BKLOG_CONFIG.get("RETENTION"),
70+
"es_shards": BKLOG_CONFIG.get("ES_SHARDS"),
71+
"storage_replicas": BKLOG_CONFIG.get("STORAGE_REPLICAS"),
72+
"time_zone": get_time_zone(),
73+
},
74+
)
75+
76+
77+
class Migration(migrations.Migration):
78+
dependencies = [
79+
("log", "0008_tenantlogconfig"),
80+
]
81+
82+
operations = [
83+
migrations.RunPython(init_tenant_log_config, reverse_code=migrations.RunPython.noop),
84+
]

apiserver/paasng/paasng/accessories/log/models.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,24 @@ class CustomCollectorConfig(UuidAuditedModel):
194194

195195
class Meta:
196196
unique_together = ("module", "name_en")
197+
198+
199+
class TenantLogConfig(UuidAuditedModel):
200+
"""租户日志配置
201+
202+
每个租户必须有一条配置记录才能使用日志平台方案
203+
可以使用 `python manage.py create_tenant_log_config` 命令创建此配置
204+
"""
205+
206+
tenant_id = tenant_id_field_factory(unique=True)
207+
208+
# ------------------- 以下字段严格匹配 bk_log 平台 ------------------ #
209+
storage_cluster_id = models.IntegerField(_("日志平台存储集群 ID"), help_text="指定该租户日志存储的 ES 集群")
210+
retention = models.IntegerField(_("日志保存时间(天)"))
211+
es_shards = models.IntegerField(_("ES 索引分片数"))
212+
storage_replicas = models.IntegerField(_("存储副本数"))
213+
214+
time_zone = models.IntegerField(
215+
_("时区"),
216+
help_text="日志时间字段解析时区,如 8 代表 UTC+8",
217+
)

apiserver/paasng/paasng/accessories/log/shim/setup_bklog.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@
1515
# We undertake not to change the open source license (MIT license) applicable
1616
# to the current version of the project delivered to anyone in the future.
1717

18-
import datetime
1918
import logging
19+
from functools import cached_property
2020
from typing import Union
2121

2222
from django.conf import settings
23-
from django.utils.timezone import get_default_timezone
2423
from django.utils.translation import gettext_lazy as _
2524

2625
from paas_wl.infras.cluster.shim import EnvClusterService
2726
from paasng.accessories.log.constants import DEFAULT_LOG_CONFIG_PLACEHOLDER
27+
from paasng.accessories.log.exceptions import TenantLogConfigNotFoundError
2828
from paasng.accessories.log.models import CustomCollectorConfig as CustomCollectorConfigModel
2929
from paasng.accessories.log.models import (
3030
ElasticSearchConfig,
3131
ElasticSearchParams,
3232
ProcessLogQueryConfig,
33+
TenantLogConfig,
3334
)
3435
from paasng.accessories.log.shim.bklog_custom_collector_config import get_or_create_custom_collector_config
3536
from paasng.accessories.log.shim.setup_elk import ELK_INGRESS_COLLECTOR_CONFIG_ID_TMPL, setup_platform_elk_config
@@ -54,32 +55,41 @@
5455
class BKLogConfigProvider:
5556
def __init__(self, module: Module):
5657
self.module = module
58+
self.tenant_id = module.application.tenant_id
59+
60+
@cached_property
61+
def config(self):
62+
"""获取租户的日志配置"""
63+
64+
try:
65+
return TenantLogConfig.objects.get(tenant_id=self.tenant_id)
66+
except TenantLogConfig.DoesNotExist:
67+
raise TenantLogConfigNotFoundError(self.tenant_id)
5768

5869
@property
5970
def timezone(self) -> int:
60-
if timezone := settings.BKLOG_CONFIG.get("TIME_ZONE"):
61-
return timezone
62-
tz = get_default_timezone()
63-
return int(tz.utcoffset(datetime.datetime.now()).total_seconds() // 60 // 60)
71+
"""获取时区"""
72+
return self.config.time_zone
6473

6574
@property
6675
def storage_cluster_id(self) -> int:
67-
return settings.BKLOG_CONFIG["STORAGE_CLUSTER_ID"]
76+
"""获取存储集群 ID(从 TenantLogConfig)"""
77+
return self.config.storage_cluster_id
6878

6979
@property
7080
def retention(self) -> int:
71-
"""获取日志存储时间(天)"""
72-
return settings.BKLOG_CONFIG["RETENTION"]
81+
"""获取日志保存时间(从 TenantLogConfig)"""
82+
return self.config.retention
7383

7484
@property
7585
def es_shards(self) -> int:
76-
"""获取ES索引分片数"""
77-
return settings.BKLOG_CONFIG["ES_SHARDS"]
86+
"""获取 ES 索引分片数(从 TenantLogConfig)"""
87+
return self.config.es_shards
7888

7989
@property
8090
def storage_replicas(self) -> int:
81-
"""获取存储副本数"""
82-
return settings.BKLOG_CONFIG["STORAGE_REPLICAS"]
91+
"""获取存储副本数(从 TenantLogConfig)"""
92+
return self.config.storage_replicas
8393

8494

8595
def _add_wildcard_suffix(path: str) -> str:

apiserver/paasng/paasng/settings/__init__.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,26 +1179,6 @@ def _build_file_handler(log_path: Path, filename: str, format: str) -> Dict:
11791179
ENABLE_BK_LOG_APIGW = settings.get("ENABLE_BK_LOG_APIGW", True)
11801180
# 蓝鲸日志平台网关的环境,仅在 ENABLE_BK_LOG_APIGW=True 时生效
11811181
BK_LOG_APIGW_SERVICE_STAGE = settings.get("BK_LOG_APIGW_SERVICE_STAGE", "stag")
1182-
# 蓝鲸日志平台相关的配置项
1183-
BKLOG_TIME_ZONE = settings.get("BKLOG_TIME_ZONE")
1184-
## 日志平台存储集群 ID
1185-
BKLOG_STORAGE_CLUSTER_ID = settings.get("BKLOG_STORAGE_CLUSTER_ID")
1186-
## 日志保存时间(天数),默认值 14
1187-
BKLOG_RETENTION = int(settings.get("BKLOG_RETENTION", 14))
1188-
## Elasticsearch 索引分片数,默认值 1
1189-
BKLOG_ES_SHARDS = int(settings.get("BKLOG_ES_SHARDS", 1))
1190-
## 存储副本数,默认值 1
1191-
BKLOG_STORAGE_REPLICAS = int(settings.get("BKLOG_STORAGE_REPLICAS", 1))
1192-
BKLOG_CONFIG = settings.get(
1193-
"BKLOG_CONFIG",
1194-
{
1195-
"TIME_ZONE": BKLOG_TIME_ZONE,
1196-
"STORAGE_CLUSTER_ID": BKLOG_STORAGE_CLUSTER_ID,
1197-
"RETENTION": BKLOG_RETENTION,
1198-
"ES_SHARDS": BKLOG_ES_SHARDS,
1199-
"STORAGE_REPLICAS": BKLOG_STORAGE_REPLICAS,
1200-
},
1201-
)
12021182

12031183
# 日志 ES 服务地址
12041184
ELASTICSEARCH_HOSTS = settings.get(

0 commit comments

Comments
 (0)