github.com/oam-dev/kubevela@v1.9.11/references/cli/system.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cli 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "strings" 24 25 "github.com/gosuri/uitable" 26 "github.com/oam-dev/cluster-gateway/pkg/generated/clientset/versioned" 27 "github.com/pkg/errors" 28 "github.com/spf13/cobra" 29 v1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/client-go/kubernetes" 34 "k8s.io/client-go/rest" 35 apiregistrationV1beta "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" 36 apiregistration "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1" 37 metrics "k8s.io/metrics/pkg/client/clientset/versioned" 38 "sigs.k8s.io/yaml" 39 40 "github.com/oam-dev/kubevela/apis/types" 41 "github.com/oam-dev/kubevela/pkg/multicluster" 42 "github.com/oam-dev/kubevela/pkg/utils/common" 43 ) 44 45 const ( 46 // FlagSpecify specifies the deployment name 47 FlagSpecify = "specify" 48 // FlagOutputFormat specifies the output format. One of: (wide | yaml) 49 FlagOutputFormat = "output" 50 // APIServiceName is the name of APIService 51 APIServiceName = "v1alpha1.cluster.core.oam.dev" 52 // UnknownMetric represent that we can't compute the metric data 53 UnknownMetric = "N/A" 54 ) 55 56 // NewSystemCommand print system detail info 57 func NewSystemCommand(c common.Args, order string) *cobra.Command { 58 cmd := &cobra.Command{ 59 Use: "system", 60 Short: "Manage system.", 61 Long: "Manage system, including printing the system deployment information in vela-system namespace and diagnosing the system's health.", 62 Example: "# Check all deployments information in all namespaces with label app.kubernetes.io/name=vela-core :\n" + 63 "> vela system info\n" + 64 "# Specify a deployment name with a namespace to check detail information:\n" + 65 "> vela system info -s kubevela-vela-core -n vela-system\n" + 66 "# Diagnose the system's health:\n" + 67 "> vela system diagnose\n", 68 Annotations: map[string]string{ 69 types.TagCommandType: types.TypeSystem, 70 types.TagCommandOrder: order, 71 }, 72 } 73 cmd.AddCommand( 74 NewSystemInfoCommand(c), 75 NewSystemDiagnoseCommand(c)) 76 return cmd 77 } 78 79 // NewSystemInfoCommand prints system detail info 80 func NewSystemInfoCommand(c common.Args) *cobra.Command { 81 cmd := &cobra.Command{ 82 Use: "info", 83 Short: "Print the system deployment detail information in all namespaces with label app.kubernetes.io/name=vela-core.", 84 Long: "Print the system deployment detail information in all namespaces with label app.kubernetes.io/name=vela-core.", 85 Args: cobra.ExactArgs(0), 86 RunE: func(cmd *cobra.Command, args []string) error { 87 // Get deploymentName from flag 88 deployName, err := cmd.Flags().GetString(FlagSpecify) 89 if err != nil { 90 return errors.Wrapf(err, "failed to get deployment name flag") 91 } 92 // Get output format from flag 93 outputFormat, err := cmd.Flags().GetString(FlagOutputFormat) 94 if err != nil { 95 return errors.Wrapf(err, "failed to get output format flag") 96 } 97 if outputFormat != "" { 98 outputFormatOptions := map[string]struct{}{ 99 "wide": {}, 100 "yaml": {}, 101 } 102 if _, exist := outputFormatOptions[outputFormat]; !exist { 103 return errors.Errorf("Outputformat must in wide | yaml !") 104 } 105 } 106 // Get kube config 107 if outputFormat != "" { 108 outputFormatOptions := map[string]struct{}{ 109 "wide": {}, 110 "yaml": {}, 111 } 112 if _, exist := outputFormatOptions[outputFormat]; !exist { 113 return errors.Errorf("Outputformat must in wide | yaml !") 114 } 115 } 116 // Get kube config 117 config, err := c.GetConfig() 118 if err != nil { 119 return err 120 } 121 // Get clientset 122 clientset, err := kubernetes.NewForConfig(config) 123 if err != nil { 124 return err 125 } 126 ctx, cancel := context.WithCancel(context.Background()) 127 defer cancel() 128 // Get deploymentsClient in all namespace 129 deployments, err := clientset.AppsV1().Deployments(metav1.NamespaceAll).List( 130 ctx, 131 metav1.ListOptions{ 132 LabelSelector: "app.kubernetes.io/name=vela-core", 133 }, 134 ) 135 if err != nil { 136 return err 137 } 138 if deployName != "" { 139 // DeployName is not empty, print the specified deployment's information 140 found := false 141 for _, deployment := range deployments.Items { 142 if deployment.Name == deployName { 143 table := SpecifiedFormatPrinter(deployment) 144 cmd.Println(table.String()) 145 found = true 146 break 147 } 148 } 149 if !found { 150 return errors.Errorf("deployment \"%s\" not found", deployName) 151 } 152 } else { 153 // Get metrics clientset 154 mc, err := metrics.NewForConfig(config) 155 if err != nil { 156 return err 157 } 158 switch outputFormat { 159 case "": 160 table, err := NormalFormatPrinter(ctx, deployments, mc) 161 if err != nil { 162 return err 163 } 164 cmd.Println(table.String()) 165 case "wide": 166 table, err := WideFormatPrinter(ctx, deployments, mc) 167 if err != nil { 168 return err 169 } 170 cmd.Println(table.String()) 171 case "yaml": 172 str, err := YamlFormatPrinter(deployments) 173 if err != nil { 174 return err 175 } 176 cmd.Println(str) 177 } 178 } 179 return nil 180 }, 181 Annotations: map[string]string{ 182 types.TagCommandType: types.TypeSystem, 183 }, 184 } 185 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.") 186 cmd.Flags().StringP(FlagOutputFormat, "o", "", "Specifies the output format. One of: (wide | yaml)") 187 return cmd 188 } 189 190 // SpecifiedFormatPrinter prints the specified deployment's information 191 func SpecifiedFormatPrinter(deployment v1.Deployment) *uitable.Table { 192 table := newUITable(). 193 AddRow("Name:", deployment.Name). 194 AddRow("Namespace:", deployment.Namespace). 195 AddRow("CreationTimestamp:", deployment.CreationTimestamp). 196 AddRow("Labels:", Map2Str(deployment.Labels)). 197 AddRow("Annotations:", Map2Str(deployment.Annotations)). 198 AddRow("Selector:", Map2Str(deployment.Spec.Selector.MatchLabels)). 199 AddRow("Image:", deployment.Spec.Template.Spec.Containers[0].Image). 200 AddRow("Args:", strings.Join(deployment.Spec.Template.Spec.Containers[0].Args, "\n")). 201 AddRow("Envs:", GetEnvVariable(deployment.Spec.Template.Spec.Containers[0].Env)). 202 AddRow("Limits:", CPUMem(deployment.Spec.Template.Spec.Containers[0].Resources.Limits)). 203 AddRow("Requests:", CPUMem(deployment.Spec.Template.Spec.Containers[0].Resources.Requests)) 204 table.MaxColWidth = 120 205 return table 206 } 207 208 // CPUMem returns the upsage of cpu and memory 209 func CPUMem(resourceList corev1.ResourceList) string { 210 b := new(bytes.Buffer) 211 fmt.Fprintf(b, "cpu=%s\n", resourceList.Cpu()) 212 fmt.Fprintf(b, "memory=%s", resourceList.Memory()) 213 return b.String() 214 } 215 216 // Map2Str converts map to string 217 func Map2Str(m map[string]string) string { 218 b := new(bytes.Buffer) 219 for key, value := range m { 220 fmt.Fprintf(b, "%s=%s\n", key, value) 221 } 222 if len(b.String()) > 1 { 223 return b.String()[:len(b.String())-1] 224 } 225 return b.String() 226 } 227 228 // NormalFormatPrinter prints information in format of normal 229 func NormalFormatPrinter(ctx context.Context, deployments *v1.DeploymentList, mc *metrics.Clientset) (*uitable.Table, error) { 230 table := newUITable().AddRow("NAME", "NAMESPACE", "READY PODS", "IMAGE", "CPU(cores)", "MEMORY(bytes)") 231 cpuMetricMap, memMetricMap := ComputeMetricByDeploymentName(ctx, deployments, mc) 232 for _, deploy := range deployments.Items { 233 table.AddRow( 234 deploy.Name, 235 deploy.Namespace, 236 fmt.Sprintf("%d/%d", deploy.Status.ReadyReplicas, deploy.Status.Replicas), 237 deploy.Spec.Template.Spec.Containers[0].Image, 238 cpuMetricMap[deploy.Name], 239 memMetricMap[deploy.Name], 240 ) 241 } 242 return table, nil 243 } 244 245 // WideFormatPrinter prints information in format of wide 246 func WideFormatPrinter(ctx context.Context, deployments *v1.DeploymentList, mc *metrics.Clientset) (*uitable.Table, error) { 247 table := newUITable().AddRow("NAME", "NAMESPACE", "READY PODS", "IMAGE", "CPU(cores)", "MEMORY(bytes)", "ARGS", "ENVS") 248 table.MaxColWidth = 100 249 cpuMetricMap, memMetricMap := ComputeMetricByDeploymentName(ctx, deployments, mc) 250 for _, deploy := range deployments.Items { 251 table.AddRow( 252 deploy.Name, 253 deploy.Namespace, 254 fmt.Sprintf("%d/%d", deploy.Status.ReadyReplicas, deploy.Status.Replicas), 255 deploy.Spec.Template.Spec.Containers[0].Image, 256 cpuMetricMap[deploy.Name], 257 memMetricMap[deploy.Name], 258 strings.Join(deploy.Spec.Template.Spec.Containers[0].Args, " "), 259 limitStringLength(GetEnvVariable(deploy.Spec.Template.Spec.Containers[0].Env), 180), 260 ) 261 } 262 return table, nil 263 } 264 265 // YamlFormatPrinter prints information in format of yaml 266 func YamlFormatPrinter(deployments *v1.DeploymentList) (string, error) { 267 str := "" 268 for _, deployment := range deployments.Items { 269 // Set ManagedFields to nil because it's too long to read 270 deployment.ManagedFields = nil 271 deploymentYaml, err := yaml.Marshal(deployment) 272 if err != nil { 273 return "", err 274 } 275 str += string(deploymentYaml) 276 } 277 return str, nil 278 } 279 280 // ComputeMetricByDeploymentName computes cpu and memory metric of deployment 281 func ComputeMetricByDeploymentName(ctx context.Context, deployments *v1.DeploymentList, mc *metrics.Clientset) (cpuMetricMap, memMetricMap map[string]string) { 282 cpuMetricMap = make(map[string]string) 283 memMetricMap = make(map[string]string) 284 podMetricsList, err := mc.MetricsV1beta1().PodMetricses(metav1.NamespaceAll).List( 285 ctx, 286 metav1.ListOptions{}, 287 ) 288 if err != nil { 289 for _, deploy := range deployments.Items { 290 cpuMetricMap[deploy.Name] = UnknownMetric 291 memMetricMap[deploy.Name] = UnknownMetric 292 } 293 return 294 } 295 296 for _, deploy := range deployments.Items { 297 cpuUsage, memUsage := int64(0), int64(0) 298 for _, pod := range podMetricsList.Items { 299 if strings.HasPrefix(pod.Name, deploy.Name) { 300 for _, container := range pod.Containers { 301 cpuUsage += container.Usage.Cpu().MilliValue() 302 memUsage += container.Usage.Memory().Value() / (1024 * 1024) 303 } 304 } 305 } 306 cpuMetricMap[deploy.Name] = fmt.Sprintf("%dm", cpuUsage) 307 memMetricMap[deploy.Name] = fmt.Sprintf("%dMi", memUsage) 308 } 309 return 310 } 311 312 // GetEnvVariable gets the environment variables 313 func GetEnvVariable(envList []corev1.EnvVar) (envStr string) { 314 for _, env := range envList { 315 envStr += fmt.Sprintf("%s=%s ", env.Name, env.Value) 316 } 317 if len(envStr) == 0 { 318 return "-" 319 } 320 return 321 } 322 323 // NewSystemDiagnoseCommand create command to help user to diagnose system's health 324 func NewSystemDiagnoseCommand(c common.Args) *cobra.Command { 325 cmd := &cobra.Command{ 326 Use: "diagnose", 327 Short: "Diagnoses system problems.", 328 Long: "Diagnoses system problems.", 329 RunE: func(cmd *cobra.Command, args []string) error { 330 // Diagnose clusters' health 331 fmt.Println("------------------------------------------------------") 332 fmt.Println("Diagnosing health of clusters...") 333 k8sClient, err := c.GetClient() 334 if err != nil { 335 return errors.Wrapf(err, "failed to get k8s client") 336 } 337 clusters, err := multicluster.ListVirtualClusters(context.Background(), k8sClient) 338 if err != nil { 339 return errors.Wrap(err, "fail to get registered cluster") 340 } 341 // Get kube config 342 config, err := c.GetConfig() 343 if err != nil { 344 return err 345 } 346 for _, cluster := range clusters { 347 clusterName := cluster.Name 348 if clusterName == multicluster.ClusterLocalName { 349 continue 350 } 351 content, err := versioned.NewForConfigOrDie(config).ClusterV1alpha1().ClusterGateways().RESTClient(clusterName).Get().AbsPath("healthz").DoRaw(context.TODO()) 352 if err != nil { 353 return errors.Wrapf(err, "failed connect cluster %s", clusterName) 354 } 355 cmd.Printf("Connect to cluster %s successfully.\n%s\n", clusterName, string(content)) 356 } 357 fmt.Println("Result: Clusters are fine~") 358 fmt.Println("------------------------------------------------------") 359 // Diagnoses the link of hub APIServer to cluster-gateway 360 fmt.Println("------------------------------------------------------") 361 fmt.Println("Diagnosing the link of hub APIServer to cluster-gateway...") 362 // Get clientset 363 clientset, err := apiregistration.NewForConfig(config) 364 if err != nil { 365 return err 366 } 367 ctx, cancel := context.WithCancel(context.Background()) 368 defer cancel() 369 apiService, err := clientset.APIServices().Get(ctx, APIServiceName, metav1.GetOptions{}) 370 if err != nil { 371 return err 372 } 373 for _, condition := range apiService.Status.Conditions { 374 if condition.Type == "Available" { 375 if condition.Status != "True" { 376 cmd.Printf("APIService \"%s\" is not available! \nMessage: %s\n", APIServiceName, condition.Message) 377 return CheckAPIService(ctx, config, apiService) 378 } 379 cmd.Printf("APIService \"%s\" is available!\n", APIServiceName) 380 } 381 } 382 fmt.Println("Result: The link of hub APIServer to cluster-gateway is fine~") 383 fmt.Println("------------------------------------------------------") 384 // Todo: Diagnose others 385 return nil 386 }, 387 Annotations: map[string]string{ 388 types.TagCommandType: types.TypeSystem, 389 }, 390 } 391 return cmd 392 } 393 394 // CheckAPIService checks the APIService 395 func CheckAPIService(ctx context.Context, config *rest.Config, apiService *apiregistrationV1beta.APIService) error { 396 svcName := apiService.Spec.Service.Name 397 svcNamespace := apiService.Spec.Service.Namespace 398 clientset, err := kubernetes.NewForConfig(config) 399 if err != nil { 400 return err 401 } 402 svc, err := clientset.CoreV1().Services(svcNamespace).Get(ctx, svcName, metav1.GetOptions{}) 403 if err != nil { 404 return err 405 } 406 set := labels.Set(svc.Spec.Selector) 407 listOptions := metav1.ListOptions{LabelSelector: set.AsSelector().String()} 408 pods, err := clientset.CoreV1().Pods(svcNamespace).List(ctx, listOptions) 409 if err != nil { 410 return err 411 } 412 if len(pods.Items) == 0 { 413 return errors.Errorf("No available pods in %s namespace with label %s.", svcNamespace, set.AsSelector().String()) 414 } 415 for _, pod := range pods.Items { 416 for _, status := range pod.Status.ContainerStatuses { 417 if !status.Ready { 418 for _, condition := range pod.Status.Conditions { 419 if condition.Status != "True" { 420 return errors.Errorf("Pod %s is not ready. Condition \"%s\" status: %s.", pod.Name, condition.Type, condition.Status) 421 } 422 } 423 return errors.Errorf("Pod %s is not ready.", pod.Name) 424 } 425 } 426 } 427 return nil 428 }