feat: 为helm chart添加WebUI的辅助容器,用于反向同步配置到ConfigMap

pull/1370/head
zhangxinhui02 2025-11-21 01:38:50 +08:00
parent 14514ba837
commit 4f6e159000
No known key found for this signature in database
GPG Key ID: 22C23383864A313F
13 changed files with 232 additions and 94 deletions

View File

@ -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)

View File

@ -1,2 +1,3 @@
adapter-cm-generator
core-webui-cm-sync
.gitlab-ci.yml

View File

@ -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"]

View File

@ -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"]

View File

@ -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()

View File

@ -0,0 +1,3 @@
toml~=0.10.2
kubernetes~=34.1.0
watchdog~=6.0.0

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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: |

View File

@ -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

View File

@ -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

View File

@ -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 # 服务端口