From 4f6e159000b5a811a139045fc0519dc3a878317a Mon Sep 17 00:00:00 2001 From: zhangxinhui02 Date: Fri, 21 Nov 2025 01:38:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BAhelm=20chart=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?WebUI=E7=9A=84=E8=BE=85=E5=8A=A9=E5=AE=B9=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=8F=8D=E5=90=91=E5=90=8C=E6=AD=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=88=B0ConfigMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helm-chart/.gitlab-ci.yml | 65 +++++--------- helm-chart/.helmignore | 1 + helm-chart/adapter-cm-generator/Dockerfile | 2 +- helm-chart/core-webui-cm-sync/Dockerfile | 10 +++ .../core-webui-cm-sync/core-webui-cm-sync.py | 89 +++++++++++++++++++ .../core-webui-cm-sync/requirements.txt | 3 + helm-chart/files/k8s-init.sh | 56 ++++++++++++ helm-chart/files/volume-linker.sh | 33 ------- helm-chart/templates/core/statefulset.yaml | 44 +++++++-- .../templates/other/configmap-scripts.yaml | 4 +- .../job-gen-adapter-cm.yaml | 2 +- .../rbac.yaml} | 12 +-- helm-chart/values.yaml | 5 ++ 13 files changed, 232 insertions(+), 94 deletions(-) create mode 100644 helm-chart/core-webui-cm-sync/Dockerfile create mode 100644 helm-chart/core-webui-cm-sync/core-webui-cm-sync.py create mode 100644 helm-chart/core-webui-cm-sync/requirements.txt create mode 100644 helm-chart/files/k8s-init.sh delete mode 100644 helm-chart/files/volume-linker.sh rename helm-chart/templates/{job-post-install => other}/job-gen-adapter-cm.yaml (93%) rename helm-chart/templates/{job-post-install/rbac-gen-adapter-cm.yaml => other/rbac.yaml} (64%) diff --git a/helm-chart/.gitlab-ci.yml b/helm-chart/.gitlab-ci.yml index e10b203a..57d17a4a 100644 --- a/helm-chart/.gitlab-ci.yml +++ b/helm-chart/.gitlab-ci.yml @@ -1,7 +1,6 @@ stages: -# - initialize-maibot-git-repo - - build - - package + - build-image + - package-helm-chart # 仅在helm-chart分支运行 workflow: @@ -9,45 +8,9 @@ workflow: - if: '$CI_COMMIT_BRANCH == "helm-chart"' - when: never -## 查询并将麦麦仓库的工作区置为最后一个tag的版本 -#initialize-maibot-git-repo: -# stage: initialize-maibot-git-repo -# image: reg.mikumikumi.xyz/base/git:latest -# cache: -# key: git-repo -# policy: push -# paths: -# - target-repo/ -# script: -# - git clone https://github.com/Mai-with-u/MaiBot.git target-repo/ -# - cd target-repo/ -# - export MAIBOT_VERSION=$(git describe --tags --abbrev=0) -# - echo "Current version is ${MAIBOT_VERSION}" -# - git reset --hard ${MAIBOT_VERSION} -# - echo ${MAIBOT_VERSION} > MAIBOT_VERSION -# - git clone https://github.com/MaiM-with-u/maim_message maim_message -# - git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM -# - ls -al -# -## 构建最后一个tag的麦麦本体的镜像 -#build-core: -# stage: build -# image: reg.mikumikumi.xyz/base/kaniko-builder:latest -# cache: -# key: git-repo -# policy: pull -# paths: -# - target-repo/ -# script: -# - cd target-repo/ -# - export BUILD_CONTEXT=$(pwd) -# - ls -al -# - export BUILD_DESTINATION="reg.mikumikumi.xyz/maibot/maibot:tag-$(cat MAIBOT_VERSION)" -# - build - -# 将Helm Chart版本作为tag,构建并推送镜像 +# 构建并推送adapter-cm-generator镜像 build-adapter-cm-generator: - stage: build + stage: build-image image: reg.mikumikumi.xyz/base/kaniko-builder:latest # rules: # - changes: @@ -60,15 +23,33 @@ build-adapter-cm-generator: - export BUILD_ARGS="--destination ${TMP_DST}:latest" - build +# 构建并推送core-webui-cm-sync镜像 +build-core-webui-cm-sync: + stage: build-image + image: reg.mikumikumi.xyz/base/kaniko-builder:latest +# rules: +# - changes: +# - helm-chart/core-webui-cm-sync/** + script: + - export BUILD_CONTEXT=helm-chart/core-webui-cm-sync + - export TMP_DST=reg.mikumikumi.xyz/maibot/core-webui-cm-sync + - export CHART_VERSION=$(cat helm-chart/Chart.yaml | grep '^version:' | cut -d' ' -f2) + - export BUILD_DESTINATION="${TMP_DST}:${CHART_VERSION}" + - export BUILD_ARGS="--destination ${TMP_DST}:latest" + - build + # 打包并推送helm chart package-helm-chart: - stage: package + stage: package-helm-chart image: reg.mikumikumi.xyz/mirror/helm:latest # rules: # - changes: # - helm-chart/files/** # - helm-chart/templates/** +# - helm-chart/.gitignore +# - helm-chart/.helmignore # - helm-chart/Chart.yaml +# - helm-chart/README.md # - helm-chart/values.yaml script: - export CHART_VERSION=$(cat helm-chart/Chart.yaml | grep '^version:' | cut -d' ' -f2) diff --git a/helm-chart/.helmignore b/helm-chart/.helmignore index 8cb2bba7..d4fe42ef 100644 --- a/helm-chart/.helmignore +++ b/helm-chart/.helmignore @@ -1,2 +1,3 @@ adapter-cm-generator +core-webui-cm-sync .gitlab-ci.yml \ No newline at end of file diff --git a/helm-chart/adapter-cm-generator/Dockerfile b/helm-chart/adapter-cm-generator/Dockerfile index 355b7b81..3149f0f5 100644 --- a/helm-chart/adapter-cm-generator/Dockerfile +++ b/helm-chart/adapter-cm-generator/Dockerfile @@ -5,6 +5,6 @@ WORKDIR /app COPY . /app -RUN pip3 install --no-cache-dir -i https://mirrors.ustc.edu.cn/pypi/simple -r requirements.txt +RUN pip3 install --no-cache-dir -r requirements.txt ENTRYPOINT ["python3", "adapter-cm-generator.py"] diff --git a/helm-chart/core-webui-cm-sync/Dockerfile b/helm-chart/core-webui-cm-sync/Dockerfile new file mode 100644 index 00000000..55a8c25b --- /dev/null +++ b/helm-chart/core-webui-cm-sync/Dockerfile @@ -0,0 +1,10 @@ +# 此镜像用于辅助麦麦的WebUI更新配置文件,随core容器持续运行 +FROM python:3.13-slim + +WORKDIR /MaiMBot + +COPY . /MaiMBot + +RUN pip3 install --no-cache-dir -r requirements.txt + +ENTRYPOINT ["python3", "core-webui-cm-sync.py"] diff --git a/helm-chart/core-webui-cm-sync/core-webui-cm-sync.py b/helm-chart/core-webui-cm-sync/core-webui-cm-sync.py new file mode 100644 index 00000000..6b95d830 --- /dev/null +++ b/helm-chart/core-webui-cm-sync/core-webui-cm-sync.py @@ -0,0 +1,89 @@ +#!/bin/python3 +# 这个程序的作用是辅助麦麦的WebUI更新配置文件,随core容器持续运行。 +# 麦麦的配置文件存储于ConfigMap中,挂载进core容器后属于只读文件,无法直接修改。 +# 此程序将core容器内的配置文件替换为可读写的中间层临时文件。启动时将实际配置文件写入,并在后台持续检测文件变化,实时同步到k8s apiServer,反向修改ConfigMap。 +# 工作目录:/MaiMBot/webui-cm-sync + +import os +import time +from datetime import datetime +from kubernetes import client, config +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +work_dir = '/MaiMBot/webui-cm-sync' +os.chdir(work_dir) + +config.load_incluster_config() +core_api = client.CoreV1Api() +with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: + namespace = f.read().strip() +release_name = os.getenv("RELEASE_NAME") +configmap_name = f'{release_name}-maibot-core' + +# 过滤列表,只监控指定文件 +target_files = { + os.path.abspath("model_config.toml"): "model_config.toml", + os.path.abspath("bot_config.toml"): "bot_config.toml" +} + + +def get_configmap(): + """获取core的ConfigMap内容""" + cm = core_api.read_namespaced_config_map(name=configmap_name, namespace=namespace) + return cm.data + + +def set_configmap(configmap_data: dict[str, str]): + """设置core的ConfigMap内容""" + core_api.patch_namespaced_config_map(configmap_name, namespace, {'data': configmap_data}) + + +class ConfigObserverHandler(FileSystemEventHandler): + """配置文件变化的事件处理器""" + def on_modified(self, event): + if os.path.abspath(event.src_path) in target_files: + print( + f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] File `{event.src_path}` was modified. Start to sync...') + with open(event.src_path, "r", encoding="utf-8") as _f: + current_data = _f.read() + new_cm = { + target_files[os.path.abspath("model_config.toml")]: current_data + } + try: + set_configmap(new_cm) + except client.exceptions.ApiException as _e: + print(f'\tError while setting configmap:\n' + f'\t\tStatus Code: {_e.status}\n' + f'\t\tReason: {_e.reason}') + + +if __name__ == '__main__': + # 初始化配置文件 + print(f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Initializing config files...') + try: + __initial_model_config = get_configmap()['model_config.toml'] + __initial_bot_config = get_configmap()['bot_config.toml'] + except client.exceptions.ApiException as e: + print(f'\tError while getting configmap:\n' + f'\t\tStatus Code: {e.status}\n' + f'\t\tReason: {e.reason}') + exit(1) + with open('model_config.toml', 'w') as f: + f.write(__initial_model_config) + with open('bot_config.toml', 'w') as f: + f.write(__initial_bot_config) + with open('ready', 'w') as f: + f.write('true') + print(f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Initializing done. Ready to sync.') + + # 持续检测变化并同步 + observer = Observer() + observer.schedule(ConfigObserverHandler(), '', recursive=False) + observer.start() + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + observer.join() diff --git a/helm-chart/core-webui-cm-sync/requirements.txt b/helm-chart/core-webui-cm-sync/requirements.txt new file mode 100644 index 00000000..58c73d28 --- /dev/null +++ b/helm-chart/core-webui-cm-sync/requirements.txt @@ -0,0 +1,3 @@ +toml~=0.10.2 +kubernetes~=34.1.0 +watchdog~=6.0.0 diff --git a/helm-chart/files/k8s-init.sh b/helm-chart/files/k8s-init.sh new file mode 100644 index 00000000..0fc26dea --- /dev/null +++ b/helm-chart/files/k8s-init.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# 此脚本用于覆盖core容器的默认启动命令,进行一些初始化 +# 1 +# 由于k8s与docker-compose的卷挂载方式有所不同,需要利用此脚本为一些文件和目录提前创建好软链接 +# /MaiMBot/data是麦麦数据的实际挂载路径 +# /MaiMBot/statistics是统计数据的实际挂载路径 +# 2 +# 此脚本等待辅助容器webui-cm-sync就绪后再启动麦麦 +# 通过检测/MaiMBot/webui-cm-sync/ready文件来判断 + +set -e +echo "[K8s Init] Preparing volume..." + +# 初次启动,在存储卷中检查并创建关键文件和目录 +mkdir -p /MaiMBot/data/plugins +mkdir -p /MaiMBot/data/logs +if [ ! -d "/MaiMBot/statistics" ] +then + echo "[K8s Init] Statistics volume disabled." +else + touch /MaiMBot/statistics/index.html +fi + +# 删除默认插件目录,准备创建用户插件目录软链接 +rm -rf /MaiMBot/plugins + +# 创建软链接,从存储卷链接到实际位置 +ln -s /MaiMBot/data/plugins /MaiMBot/plugins +ln -s /MaiMBot/data/logs /MaiMBot/logs +if [ -f "/MaiMBot/statistics/index.html" ] +then + ln -s /MaiMBot/statistics/index.html /MaiMBot/maibot_statistics.html +fi + +echo "[K8s Init] Volume ready." + +# 如果启用了WebUI,则等待辅助容器webui-cm-sync就绪,然后创建中间层配置文件软链接 +if [ "$MAIBOT_WEBUI_ENABLED" = "true" ] +then + echo "[K8s Init] WebUI enabled. Waiting for container 'webui-cm-sync' ready..." + while [ ! -f /MaiMBot/webui-cm-sync/ready ]; do + sleep 1 + done + echo "[K8s Init] Container 'webui-cm-sync' ready." + mkdir -p /MaiMBot/config + ln -s /MaiMBot/webui-cm-sync/model_config.toml /MaiMBot/config/model_config.toml + ln -s /MaiMBot/webui-cm-sync/bot_config.toml /MaiMBot/config/bot_config.toml + echo "[K8s Init] Config files middle layer for WebUI created." +else + echo "[K8s Init] WebUI disabled." +fi + +# 启动麦麦 +echo "[K8s Init] Waking up MaiBot..." +echo +exec python bot.py diff --git a/helm-chart/files/volume-linker.sh b/helm-chart/files/volume-linker.sh deleted file mode 100644 index 4a371e3c..00000000 --- a/helm-chart/files/volume-linker.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# 此脚本用于覆盖core容器的默认启动命令 -# 由于k8s与docker-compose的卷挂载方式有所不同,需要利用此脚本为一些文件和目录提前创建好软链接 -# /MaiMBot/data是麦麦数据的实际挂载路径 -# /MaiMBot/statistics是统计数据的实际挂载路径 - -set -e -echo "[VolumeLinker] Preparing volume..." - -# 初次启动,在存储卷中检查并创建关键文件和目录 -mkdir -p /MaiMBot/data/plugins -mkdir -p /MaiMBot/data/logs -if [ ! -d "/MaiMBot/statistics" ] -then - echo "[VolumeLinker] Statistics volume disabled." -else - touch /MaiMBot/statistics/index.html -fi - -# 删除空的插件目录,准备创建软链接 -rm -rf /MaiMBot/plugins - -# 创建软链接,从存储卷链接到实际位置 -ln -s /MaiMBot/data/plugins /MaiMBot/plugins -ln -s /MaiMBot/data/logs /MaiMBot/logs -if [ -f "/MaiMBot/statistics/index.html" ] -then - ln -s /MaiMBot/statistics/index.html /MaiMBot/maibot_statistics.html -fi - -# 启动麦麦 -echo "[VolumeLinker] Starting MaiBot..." -exec python bot.py diff --git a/helm-chart/templates/core/statefulset.yaml b/helm-chart/templates/core/statefulset.yaml index d841c989..248f6a73 100644 --- a/helm-chart/templates/core/statefulset.yaml +++ b/helm-chart/templates/core/statefulset.yaml @@ -18,17 +18,21 @@ spec: spec: containers: - name: core - command: # 为了在k8s中初始化存储卷,这里替换启动命令为指定脚本 + command: # 为了在k8s中初始化,这里替换启动命令为指定脚本 - sh args: - - /MaiMBot/volume-linker.sh + - /MaiMBot/k8s-init.sh env: - name: TZ - value: Asia/Shanghai + value: "Asia/Shanghai" - name: EULA_AGREE - value: 99f08e0cab0190de853cb6af7d64d4de + value: "99f08e0cab0190de853cb6af7d64d4de" - name: PRIVACY_AGREE - value: 9943b855e72199d0f5016ea39052f1b6 + value: "9943b855e72199d0f5016ea39052f1b6" + {{- if .Values.core.webui.enabled }} + - name: MAIBOT_WEBUI_ENABLED + value: "true" + {{- end}} image: {{ .Values.core.image.repository | default "sengokucola/maibot" }}:{{ .Values.core.image.tag | default "0.11.5-beta" }} imagePullPolicy: {{ .Values.core.image.pullPolicy }} ports: @@ -47,14 +51,15 @@ spec: volumeMounts: - mountPath: /MaiMBot/data name: data - - mountPath: /MaiMBot/volume-linker.sh + - mountPath: /MaiMBot/k8s-init.sh name: scripts readOnly: true - subPath: volume-linker.sh + subPath: k8s-init.sh - mountPath: /MaiMBot/.env name: config readOnly: true subPath: .env + {{- if not .Values.core.webui.enabled }} - mountPath: /MaiMBot/config/model_config.toml name: config readOnly: true @@ -63,10 +68,26 @@ spec: name: config readOnly: true subPath: bot_config.toml + {{- end }} {{- if .Values.statistics_dashboard.enabled }} - mountPath: /MaiMBot/statistics name: statistics {{- end }} + {{- if .Values.core.webui.enabled }} + - mountPath: /MaiMBot/webui-cm-sync + name: webui-cm-sync + {{- end }} + {{- if .Values.core.webui.enabled }} + - name: webui-cm-sync + image: {{ .Values.core.webui.cm_sync.image.repository | default "reg.mikumikumi.xyz/maibot/core-webui-cm-sync" }}:{{ .Values.core.webui.cm_sync.image.tag | default "0.11.5-beta" }} + imagePullPolicy: {{ .Values.core.webui.cm_sync.image.pullPolicy }} + env: + - name: RELEASE_NAME + value: {{ .Release.Name }} + volumeMounts: + - mountPath: /MaiMBot/webui-cm-sync + name: webui-cm-sync + {{- end }} {{- if .Values.core.setup_default_plugins }} initContainers: # 用户插件目录存储在存储卷中,会在启动时覆盖掉容器的默认插件目录。此初始化容器用于默认插件更新后或麦麦首次启动时为用户自动安装默认插件到存储卷中 - args: @@ -86,6 +107,7 @@ spec: readOnly: true subPath: setup-plugins.py {{- end }} + serviceAccountName: {{ .Release.Name }}-maibot-sa {{- if .Values.core.image.pullSecrets }} imagePullSecrets: {{ toYaml .Values.core.image.pullSecrets | nindent 8 }} @@ -104,8 +126,8 @@ spec: claimName: {{ .Release.Name }}-maibot-core - configMap: items: - - key: volume-linker.sh - path: volume-linker.sh + - key: k8s-init.sh + path: k8s-init.sh {{- if .Values.core.setup_default_plugins }} - key: setup-plugins.py path: setup-plugins.py @@ -127,3 +149,7 @@ spec: persistentVolumeClaim: claimName: {{ .Release.Name }}-maibot-statistics-dashboard {{- end }} + {{- if .Values.core.webui.enabled }} + - emptyDir: {} + name: webui-cm-sync + {{- end }} diff --git a/helm-chart/templates/other/configmap-scripts.yaml b/helm-chart/templates/other/configmap-scripts.yaml index 4d1e546f..dd61ca68 100644 --- a/helm-chart/templates/other/configmap-scripts.yaml +++ b/helm-chart/templates/other/configmap-scripts.yaml @@ -5,8 +5,8 @@ metadata: namespace: {{ .Release.Namespace }} data: # core - volume-linker.sh: | - {{ .Files.Get "files/volume-linker.sh" | nindent 4 }} + k8s-init.sh: | + {{ .Files.Get "files/k8s-init.sh" | nindent 4 }} # core的初始化容器 {{- if .Values.core.setup_default_plugins }} setup-plugins.py: | diff --git a/helm-chart/templates/job-post-install/job-gen-adapter-cm.yaml b/helm-chart/templates/other/job-gen-adapter-cm.yaml similarity index 93% rename from helm-chart/templates/job-post-install/job-gen-adapter-cm.yaml rename to helm-chart/templates/other/job-gen-adapter-cm.yaml index bbc49c83..ad01b568 100644 --- a/helm-chart/templates/job-post-install/job-gen-adapter-cm.yaml +++ b/helm-chart/templates/other/job-gen-adapter-cm.yaml @@ -11,7 +11,7 @@ spec: backoffLimit: 2 template: spec: - serviceAccountName: {{ .Release.Name }}-maibot-adapter-cm-generator + serviceAccountName: {{ .Release.Name }}-maibot-sa restartPolicy: Never containers: - name: adapter-cm-generator diff --git a/helm-chart/templates/job-post-install/rbac-gen-adapter-cm.yaml b/helm-chart/templates/other/rbac.yaml similarity index 64% rename from helm-chart/templates/job-post-install/rbac-gen-adapter-cm.yaml rename to helm-chart/templates/other/rbac.yaml index 1df08343..8b8075c7 100644 --- a/helm-chart/templates/job-post-install/rbac-gen-adapter-cm.yaml +++ b/helm-chart/templates/other/rbac.yaml @@ -1,14 +1,14 @@ -# 动态生成adapter配置文件的configmap所需要的rbac授权 +# 初始化及反向修改ConfigMap所需要的rbac授权 apiVersion: v1 kind: ServiceAccount metadata: - name: {{ .Release.Name }}-maibot-adapter-cm-generator + name: {{ .Release.Name }}-maibot-sa namespace: {{ .Release.Namespace }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: {{ .Release.Name }}-maibot-adapter-cm-gen-role + name: {{ .Release.Name }}-maibot-role namespace: {{ .Release.Namespace }} rules: - apiGroups: [""] @@ -21,13 +21,13 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ .Release.Name }}-maibot-adapter-cm-gen-role-binding + name: {{ .Release.Name }}-maibot-rolebinding namespace: {{ .Release.Namespace }} subjects: - kind: ServiceAccount - name: {{ .Release.Name }}-maibot-adapter-cm-generator + name: {{ .Release.Name }}-maibot-sa namespace: {{ .Release.Namespace }} roleRef: kind: Role - name: {{ .Release.Name }}-maibot-adapter-cm-gen-role + name: {{ .Release.Name }}-maibot-role apiGroup: rbac.authorization.k8s.io diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml index 5f32b1c1..16ba0bc7 100644 --- a/helm-chart/values.yaml +++ b/helm-chart/values.yaml @@ -67,6 +67,11 @@ core: webui: # WebUI相关配置 enabled: true # 默认启用 + cm_sync: # WebUI的辅助容器配置 + image: + repository: # 默认 reg.mikumikumi.xyz/maibot/core-webui-cm-sync + tag: # 默认 0.11.5-beta + pullPolicy: IfNotPresent service: type: ClusterIP # ClusterIP / NodePort 指定NodePort可以将内网的服务端口映射到物理节点的端口 port: 8001 # 服务端口