Files
kubevela/references/cli/system.go
47 8a82ac6277 Feat: System Info & Diagnose (#4379)
* 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
2022-07-29 13:47:35 +08:00

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
}