feat: complete Loki query, insights, settings and deployment workflows
Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application. Validated with go test ./..., npm run build, and npm run lint.
This commit is contained in:
1 parent
f9c6c1d826
commit
2d73661dd1
127 files changed
+8117
-2876
No files matched your search
@@ -2,13 +2,13 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"opsxloki/models"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClusterInfo struct {
|
||||
@@ -30,72 +30,21 @@ type NodeInfo struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// GetClusterInfo 通过 kubectl 查询集群信息。
|
||||
// Token 接入的集群在创建时已构造等价 kubeconfig,因此 kubectl 同样适用。
|
||||
// GetClusterInfo 从 API Server 读取集群信息,context 列表由 kubeconfig 本地解析。
|
||||
func GetClusterInfo(ctx context.Context, cluster *models.Cluster) (*ClusterInfo, error) {
|
||||
kubeconfig, err := resolveKubeconfig(cluster)
|
||||
client, err := clusterClient(cluster, 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kubeFile, err := os.CreateTemp("", "kube-*.yaml")
|
||||
info := &ClusterInfo{Nodes: []NodeInfo{}, Namespaces: []string{}, Contexts: []string{}}
|
||||
if version, err := client.Discovery().ServerVersion(); err == nil {
|
||||
info.Version = version.GitVersion
|
||||
}
|
||||
nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("获取节点列表失败: %w", err)
|
||||
}
|
||||
defer os.Remove(kubeFile.Name())
|
||||
kubeFile.WriteString(kubeconfig)
|
||||
kubeFile.Close()
|
||||
|
||||
env := append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeFile.Name()))
|
||||
|
||||
info := &ClusterInfo{
|
||||
Nodes: []NodeInfo{},
|
||||
Namespaces: []string{},
|
||||
Contexts: []string{},
|
||||
}
|
||||
|
||||
// Version(best-effort)
|
||||
if versionOut, err := runCmd(ctx, env, "kubectl", "version", "-o", "json"); err == nil {
|
||||
var versionData struct {
|
||||
ServerVersion struct {
|
||||
GitVersion string `json:"gitVersion"`
|
||||
} `json:"serverVersion"`
|
||||
}
|
||||
json.Unmarshal(versionOut, &versionData)
|
||||
info.Version = versionData.ServerVersion.GitVersion
|
||||
}
|
||||
|
||||
// Nodes
|
||||
nodesOut, err := runCmd(ctx, env, "kubectl", "get", "nodes", "-o", "json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取节点列表失败: %v", err)
|
||||
}
|
||||
var nodesData struct {
|
||||
Items []struct {
|
||||
Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
} `json:"metadata"`
|
||||
Status struct {
|
||||
Conditions []struct {
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
} `json:"conditions"`
|
||||
NodeInfo struct {
|
||||
KubeletVersion string `json:"kubeletVersion"`
|
||||
OperatingSystem string `json:"operatingSystem"`
|
||||
Architecture string `json:"architecture"`
|
||||
} `json:"nodeInfo"`
|
||||
Addresses []struct {
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
} `json:"addresses"`
|
||||
} `json:"status"`
|
||||
} `json:"items"`
|
||||
}
|
||||
json.Unmarshal(nodesOut, &nodesData)
|
||||
|
||||
for _, item := range nodesData.Items {
|
||||
for _, item := range nodes.Items {
|
||||
status := "Unknown"
|
||||
for _, cond := range item.Status.Conditions {
|
||||
if cond.Type == "Ready" {
|
||||
@@ -107,58 +56,43 @@ func GetClusterInfo(ctx context.Context, cluster *models.Cluster) (*ClusterInfo,
|
||||
break
|
||||
}
|
||||
}
|
||||
var roles []string
|
||||
for label := range item.Metadata.Labels {
|
||||
roles := []string{}
|
||||
for label := range item.Labels {
|
||||
if strings.HasPrefix(label, "node-role.kubernetes.io/") {
|
||||
roles = append(roles, label[len("node-role.kubernetes.io/"):])
|
||||
roles = append(roles, strings.TrimPrefix(label, "node-role.kubernetes.io/"))
|
||||
}
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
roles = []string{"worker"}
|
||||
}
|
||||
internalIP := ""
|
||||
for _, addr := range item.Status.Addresses {
|
||||
if addr.Type == "InternalIP" {
|
||||
internalIP = addr.Address
|
||||
sort.Strings(roles)
|
||||
ip := ""
|
||||
for _, address := range item.Status.Addresses {
|
||||
if address.Type == "InternalIP" {
|
||||
ip = address.Address
|
||||
break
|
||||
}
|
||||
}
|
||||
info.Nodes = append(info.Nodes, NodeInfo{
|
||||
Name: item.Metadata.Name,
|
||||
Status: status,
|
||||
Roles: roles,
|
||||
Version: item.Status.NodeInfo.KubeletVersion,
|
||||
OS: item.Status.NodeInfo.OperatingSystem,
|
||||
Arch: item.Status.NodeInfo.Architecture,
|
||||
InternalIP: internalIP,
|
||||
Labels: item.Metadata.Labels,
|
||||
})
|
||||
info.Nodes = append(info.Nodes, NodeInfo{Name: item.Name, Status: status, Roles: roles, Version: item.Status.NodeInfo.KubeletVersion, OS: item.Status.NodeInfo.OperatingSystem, Arch: item.Status.NodeInfo.Architecture, InternalIP: ip, Labels: item.Labels})
|
||||
}
|
||||
|
||||
// Namespaces(best-effort)
|
||||
if nsOut, err := runCmd(ctx, env, "kubectl", "get", "ns", "-o", "jsonpath={.items[*].metadata.name}"); err == nil {
|
||||
info.Namespaces = strings.Fields(string(nsOut))
|
||||
if namespaces, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}); err == nil {
|
||||
for _, item := range namespaces.Items {
|
||||
info.Namespaces = append(info.Namespaces, item.Name)
|
||||
}
|
||||
sort.Strings(info.Namespaces)
|
||||
}
|
||||
|
||||
// Contexts(best-effort)
|
||||
if ctxOut, err := runCmd(ctx, env, "kubectl", "config", "get-contexts", "-o", "name"); err == nil {
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(ctxOut)), "\n") {
|
||||
if line = strings.TrimSpace(line); line != "" {
|
||||
info.Contexts = append(info.Contexts, line)
|
||||
source, err := resolveKubeconfig(cluster)
|
||||
if err == nil {
|
||||
if raw, err := clientcmd.Load([]byte(source)); err == nil {
|
||||
for name := range raw.Contexts {
|
||||
info.Contexts = append(info.Contexts, name)
|
||||
}
|
||||
sort.Strings(info.Contexts)
|
||||
info.CurrentCtx = raw.CurrentContext
|
||||
if cluster.Context != "" {
|
||||
info.CurrentCtx = cluster.Context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current context(best-effort)
|
||||
if currentCtxOut, err := runCmd(ctx, env, "kubectl", "config", "current-context"); err == nil {
|
||||
info.CurrentCtx = strings.TrimSpace(string(currentCtxOut))
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func runCmd(ctx context.Context, env []string, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Env = env
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
Reference in new issue
Block a user