mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-11 03:47:59 +00:00
* Feat: System Info & Diagnose Signed-off-by: foursevenlove <foursevenlove@gmail.com> * Fix:1.misspelling 2.license Signed-off-by: foursevenlove <foursevenlove@gmail.com> * Fix: pattern of imported package Signed-off-by: foursevenlove <foursevenlove@gmail.com> * Fix: pattern of imported package Signed-off-by: foursevenlove <foursevenlove@gmail.com> * Fix:1.return error instead of panic 2.get deployment by label instead of by namespace 3.when getting a single deployment, the result is displayed in multi rows. Feat: 1.the system info command displays the cpu and memory metrics 2.the system info command displays the numbers of ready pods and desired pods. * Feat: 1.the system info command displays the environment variables * Fix: Making syntax simple
252 lines
8.9 KiB
Go
252 lines
8.9 KiB
Go
/*
|
|
Copyright 2021 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/oam-dev/cluster-gateway/pkg/generated/clientset/versioned"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
v1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
|
metrics "k8s.io/metrics/pkg/client/clientset/versioned"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/multicluster"
|
|
"github.com/oam-dev/kubevela/pkg/utils/common"
|
|
)
|
|
|
|
const (
|
|
// FlagSpecify specifies the deployment name
|
|
FlagSpecify = "specify"
|
|
// FlagDeploymentNamespace specifies the namespace of deployment
|
|
FlagDeploymentNamespace = "namespace"
|
|
)
|
|
|
|
// NewSystemCommand print system detail info
|
|
func NewSystemCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "system",
|
|
Short: "Manage system.",
|
|
Long: "Manage system, incluing printing the system deployment information in vela-system namespace and diagnosing the system's health.",
|
|
Example: "# Check all deployments information in all namespaces with label app.kubernetes.io/name=vela-core :\n" +
|
|
"> vela system info\n" +
|
|
"# Specify a deployment name with a namespace to check detail information:\n" +
|
|
"> vela system info -s kubevela-vela-core -n vela-system\n" +
|
|
"# Diagnose the system's health:\n" +
|
|
"> vela system diagnose\n",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeSystem,
|
|
},
|
|
}
|
|
cmd.AddCommand(
|
|
NewSystemInfoCommand(c),
|
|
NewSystemDiagnoseCommand(c))
|
|
return cmd
|
|
}
|
|
|
|
// NewSystemInfoCommand prints system detail info
|
|
func NewSystemInfoCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "info",
|
|
Short: "Print the system deployment detail information in all namespaces with label app.kubernetes.io/name=vela-core.",
|
|
Long: "Print the system deployment detail information in all namespaces with label app.kubernetes.io/name=vela-core.",
|
|
Args: cobra.ExactValidArgs(0),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// Get deploymentName from flag
|
|
deployName, err := cmd.Flags().GetString(FlagSpecify)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get deployment name flag")
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagDeploymentNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get deployment namespace flag")
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Get clientset
|
|
clientset, err := kubernetes.NewForConfig(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mc, err := metrics.NewForConfig(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
if deployName != "" {
|
|
// DeployName is not empty, print the specified deployment's information in yaml type
|
|
if namespace == "" {
|
|
return errors.Errorf("An empty namespace can not be set when a resource name is provided. Use -n to specify the namespace.")
|
|
}
|
|
// Get specified deployment in specified namespace
|
|
deployment, err := clientset.AppsV1().Deployments(namespace).Get(
|
|
ctx,
|
|
deployName,
|
|
metav1.GetOptions{},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Set ManagedFields to nil because it's too long to read
|
|
deployment.ManagedFields = nil
|
|
deploymentYaml, _ := yaml.Marshal(deployment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.Println(string(deploymentYaml))
|
|
} else {
|
|
// DeployName is empty, print all deployment's partial args
|
|
// Get deploymentsClient in all namespace
|
|
deployments, err := clientset.AppsV1().Deployments(metav1.NamespaceAll).List(
|
|
ctx,
|
|
metav1.ListOptions{
|
|
LabelSelector: "app.kubernetes.io/name=vela-core",
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
podMetricsList, err := mc.MetricsV1beta1().PodMetricses(metav1.NamespaceAll).List(
|
|
ctx,
|
|
metav1.ListOptions{},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
table := newUITable().AddRow("NAME", "NAMESPACE", "READY PODS", "IMAGE", "CPU(cores)", "MEMORY(bytes)", "ARGS", "ENVS")
|
|
cpuMetricMap, memMetricMap := ComputeMetricByDeploymentName(deployments, podMetricsList)
|
|
for _, deploy := range deployments.Items {
|
|
table.AddRow(
|
|
deploy.Name,
|
|
deploy.Namespace,
|
|
fmt.Sprintf("%d/%d", deploy.Status.ReadyReplicas, deploy.Status.Replicas),
|
|
deploy.Spec.Template.Spec.Containers[0].Image,
|
|
fmt.Sprintf("%dm", cpuMetricMap[deploy.Name]),
|
|
fmt.Sprintf("%dMi", memMetricMap[deploy.Name]),
|
|
limitStringLength(strings.Join(deploy.Spec.Template.Spec.Containers[0].Args, " "), 50),
|
|
limitStringLength(GetEnvVariable(deploy.Spec.Template.Spec.Containers[0].Env), 50),
|
|
)
|
|
}
|
|
cmd.Println(table.String())
|
|
}
|
|
return nil
|
|
},
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeSystem,
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagSpecify, "s", "", "Specify the name of the deployment to check detail information. If empty, it will print all deployments information. Default to be empty.")
|
|
cmd.Flags().StringP(FlagDeploymentNamespace, "n", "", "Specify the namespace of the deployment to check detail information. If empty, it will prints deployments information of all namespaces. Default to be empty. An empty namespace may not be can when a resource name is provided. ")
|
|
return cmd
|
|
}
|
|
|
|
// ComputeMetricByDeploymentName computes cpu and memory metric of deployment
|
|
func ComputeMetricByDeploymentName(deployments *v1.DeploymentList, podMetricsList *v1beta1.PodMetricsList) (cpuMetricMap, memMetricMap map[string]int64) {
|
|
cpuMetricMap = make(map[string]int64)
|
|
memMetricMap = make(map[string]int64)
|
|
for _, deploy := range deployments.Items {
|
|
cpuUsage, memUsage := int64(0), int64(0)
|
|
for _, pod := range podMetricsList.Items {
|
|
if strings.HasPrefix(pod.Name, deploy.Name) {
|
|
for _, container := range pod.Containers {
|
|
cpuUsage += container.Usage.Cpu().MilliValue()
|
|
memUsage += container.Usage.Memory().Value() / (1024 * 1024)
|
|
}
|
|
}
|
|
}
|
|
cpuMetricMap[deploy.Name] = cpuUsage
|
|
memMetricMap[deploy.Name] = memUsage
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetEnvVariable gets the environment variables
|
|
func GetEnvVariable(envList []corev1.EnvVar) (envStr string) {
|
|
for _, env := range envList {
|
|
envStr += fmt.Sprintf("%s=%s ", env.Name, env.Value)
|
|
}
|
|
if len(envStr) == 0 {
|
|
return "-"
|
|
}
|
|
return
|
|
}
|
|
|
|
// NewSystemDiagnoseCommand create command to help user to diagnose system's health
|
|
func NewSystemDiagnoseCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "diagnose",
|
|
Short: "Diagnoses system problems.",
|
|
Long: "Diagnoses system problems.",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// Diagnoses APIService of cluster-gateway
|
|
fmt.Println("------------------------------------------------------")
|
|
fmt.Println("Diagnosing APIService of cluster-gateway...")
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
_, err = multicluster.GetClusterGatewayService(context.Background(), k8sClient)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get cluster secret namespace, please ensure cluster gateway is correctly deployed")
|
|
}
|
|
fmt.Println("Result: APIService of cluster-gateway is fine~")
|
|
fmt.Println("------------------------------------------------------")
|
|
// Diagnose clusters' health
|
|
fmt.Println("------------------------------------------------------")
|
|
fmt.Println("Diagnosing health of clusters...")
|
|
clusters, err := multicluster.ListVirtualClusters(context.Background(), k8sClient)
|
|
if err != nil {
|
|
return errors.Wrap(err, "fail to get registered cluster")
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, cluster := range clusters {
|
|
clusterName := cluster.Name
|
|
if clusterName == multicluster.ClusterLocalName {
|
|
continue
|
|
}
|
|
content, err := versioned.NewForConfigOrDie(config).ClusterV1alpha1().ClusterGateways().RESTClient(clusterName).Get().AbsPath("healthz").DoRaw(context.TODO())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed connect cluster %s", clusterName)
|
|
}
|
|
cmd.Printf("Connect to cluster %s successfully.\n%s\n", clusterName, string(content))
|
|
}
|
|
fmt.Println("Result: Clusters are fine~")
|
|
fmt.Println("------------------------------------------------------")
|
|
// Todo: Diagnose others
|
|
return nil
|
|
},
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeSystem,
|
|
},
|
|
}
|
|
return cmd
|
|
}
|