consul修改为单个配置,不走环境变量,新增k3s获取环境变量

This commit is contained in:
2026-04-13 15:19:08 +08:00
parent 1b85b42e78
commit f09cc8640d
4 changed files with 394 additions and 58 deletions

222
k3sconfig/k3sconfig.go Normal file
View File

@@ -0,0 +1,222 @@
package k3sconfig
import (
"context"
"fmt"
"os"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/r3labs/diff/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type K8sConfig struct {
ApiServer string `json:"apiServer"`
Token string `json:"token"`
Kubeconfig string `json:"kubeconfig"`
Namespace string `json:"namespace"`
}
func init() {
ctx := context.Background()
consulAddr := g.Cfg().MustGet(ctx, "consul.address").String()
if consulAddr != "" {
g.Log().Debug(ctx, "📄 [K3sConfig] 检测到 consul.address 配置,使用 Consul跳过 K3s 加载")
return
}
loadFromK3sCluster(ctx)
}
func loadFromK3sCluster(ctx context.Context) {
serviceName := g.Cfg().MustGet(ctx, "server.name", "").String()
if serviceName == "" {
panic("❌ [K3sConfig] 配置文件中未设置 server.name")
}
k8sConfig := getK8sConfig(ctx)
namespace := k8sConfig.Namespace
if namespace == "" {
namespace = "default"
}
configMapName := fmt.Sprintf("%s-config", serviceName)
g.Log().Info(ctx, "🔗 [K3sConfig] 从 K3s 集群加载配置:", configMapName)
var config *rest.Config
var err error
if k8sConfig.ApiServer != "" && k8sConfig.Token != "" {
g.Log().Infof(ctx, "📍 [K3sConfig] 使用远程 K8s API Server: %s", k8sConfig.ApiServer)
config = &rest.Config{
Host: k8sConfig.ApiServer,
BearerToken: k8sConfig.Token,
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
}
} else if k8sConfig.Kubeconfig != "" {
g.Log().Infof(ctx, "📍 [K3sConfig] 使用 Kubeconfig 文件: %s", k8sConfig.Kubeconfig)
config, err = clientcmd.BuildConfigFromFlags("", k8sConfig.Kubeconfig)
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 构建 K8s 配置失败: %v", err))
}
} else {
kubeconfigEnv := os.Getenv("KUBECONFIG")
if kubeconfigEnv != "" {
g.Log().Infof(ctx, "📍 [K3sConfig] 使用环境变量 KUBECONFIG: %s", kubeconfigEnv)
config, err = clientcmd.BuildConfigFromFlags("", kubeconfigEnv)
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 构建 K8s 配置失败: %v", err))
}
} else {
home, _ := os.UserHomeDir()
defaultKubeconfig := fmt.Sprintf("%s/.kube/config", home)
g.Log().Infof(ctx, "📍 [K3sConfig] 使用默认 Kubeconfig: %s", defaultKubeconfig)
config, err = clientcmd.BuildConfigFromFlags("", defaultKubeconfig)
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 构建 K8s 配置失败: %v", err))
}
}
}
g.Log().Infof(ctx, "🔌 [K3sConfig] K8s API Server: %s", config.Host)
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 创建 K8s 客户端失败: %v", err))
}
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, configMapName, metav1.GetOptions{})
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 获取 ConfigMap 失败: %v", err))
}
g.Log().Infof(ctx, "📦 [K3sConfig] ConfigMap 信息 - Name: %s, Namespace: %s, ResourceVersion: %s",
cm.Name, cm.Namespace, cm.ResourceVersion)
configData, ok := cm.Data["config.yml"]
if !ok {
g.Log().Debugf(ctx, "📋 [K3sConfig] ConfigMap 可用键: %v", getMapKeys(cm.Data))
panic("❌ [K3sConfig] ConfigMap 中未找到 config.yml 键")
}
g.Log().Infof(ctx, "📄 [K3sConfig] 获取到的配置内容长度: %d 字节", len(configData))
adapter, err := gcfg.NewAdapterContent()
if err != nil {
panic(fmt.Sprintf("❌ [K3sConfig] 创建配置适配器失败: %v", err))
}
adapter.SetContent(configData)
g.Cfg().SetAdapter(adapter)
g.Log().Infof(ctx, "✅ [K3sConfig] 成功从 K3s 加载配置: %s/%s", namespace, configMapName)
go watchK3sConfig(ctx, clientset, namespace, configMapName, cm.ResourceVersion)
}
func getK8sConfig(ctx context.Context) K8sConfig {
return K8sConfig{
ApiServer: g.Cfg().MustGet(ctx, "k8s.apiServer", "").String(),
Token: g.Cfg().MustGet(ctx, "k8s.token", "").String(),
Kubeconfig: g.Cfg().MustGet(ctx, "k8s.kubeconfig", "").String(),
Namespace: g.Cfg().MustGet(ctx, "k8s.namespace", "default").String(),
}
}
func watchK3sConfig(ctx context.Context, clientset *kubernetes.Clientset, namespace, configMapName string, lastResourceVersion string) {
g.Log().Info(ctx, "👀 [K3sConfig] 开始监听 ConfigMap 变更...")
for {
time.Sleep(10 * time.Second)
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, configMapName, metav1.GetOptions{
ResourceVersion: "0",
})
if err != nil {
g.Log().Errorf(ctx, "❌ [K3sConfig] 监听 ConfigMap 失败: %v", err)
continue
}
if cm.ResourceVersion == lastResourceVersion {
continue
}
g.Log().Infof(ctx, "🔔 [K3sConfig] 检测到 ConfigMap 变更, Old Version: %s, New Version: %s",
lastResourceVersion, cm.ResourceVersion)
configData, ok := cm.Data["config.yml"]
if !ok {
g.Log().Error(ctx, "❌ [K3sConfig] ConfigMap 中未找到 config.yml 键")
continue
}
updateK3sConfig(configData)
lastResourceVersion = cm.ResourceVersion
}
}
func updateK3sConfig(content string) {
ctx := context.Background()
oldConfig, _ := g.Cfg().Data(ctx)
adapter, err := gcfg.NewAdapterContent()
if err != nil {
g.Log().Errorf(ctx, "❌ [K3sConfig] 创建配置适配器失败: %v", err)
return
}
adapter.SetContent(content)
g.Cfg().SetAdapter(adapter)
newConfig, _ := g.Cfg().Data(ctx)
changelog, err := diff.Diff(oldConfig, newConfig)
if err != nil {
g.Log().Errorf(ctx, "❌ [K3sConfig] 配置对比失败: %v", err)
return
}
if len(changelog) == 0 {
g.Log().Info(ctx, "✅ [K3sConfig] 配置已热更新成功(内容无实质变化)")
return
}
g.Log().Infof(ctx, "=== [K3sConfig] 检测到配置变更 (%d 项) ===", len(changelog))
for _, change := range changelog {
switch change.Type {
case "create":
g.Log().Infof(ctx, "[+] 新增: %s = %v", change.Path, change.To)
case "update":
g.Log().Infof(ctx, "[~] 修改: %s: %v -> %v", change.Path, change.From, change.To)
case "delete":
g.Log().Infof(ctx, "[-] 删除: %s (原值: %v)", change.Path, change.From)
}
}
g.Log().Info(ctx, "=================================")
}
func getMapKeys(m map[string]string) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func loadConfigFromFile(ctx context.Context, configPath string) {
adapter, err := gcfg.NewAdapterFile(configPath)
if err != nil {
g.Log().Errorf(ctx, "❌ [K3sConfig] 创建文件适配器失败: %v", err)
return
}
g.Cfg().SetAdapter(adapter)
g.Log().Info(ctx, "✅ [K3sConfig] 已成功加载配置:", configPath)
}