Skip to content

Commit

Permalink
Merge pull request #2120 from liangzai006/master
Browse files Browse the repository at this point in the history
support 'cluster-info dump' command
  • Loading branch information
ks-ci-bot authored Feb 13, 2024
2 parents c632035 + 877c9e4 commit fa646fb
Show file tree
Hide file tree
Showing 11 changed files with 838 additions and 27 deletions.
162 changes: 162 additions & 0 deletions cmd/kk/apis/kubekey/v1alpha2/multicluster_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package v1alpha2

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

type MultiClusterSpec struct {
// Join cluster as a kubefed cluster
JoinFederation bool `json:"joinFederation,omitempty"`

// Desired state of the cluster
Enable bool `json:"enable,omitempty"`

// Provider of the cluster, this field is just for description
Provider string `json:"provider,omitempty"`

// Connection holds info to connect to the member cluster
Connection Connection `json:"connection,omitempty"`

// ExternalKubeAPIEnabled export kubeapiserver to public use a lb type service if connection type is proxy
ExternalKubeAPIEnabled bool `json:"externalKubeAPIEnabled,omitempty"`
}

type ConnectionType string

const (
ConnectionTypeDirect ConnectionType = "direct"
ConnectionTypeProxy ConnectionType = "proxy"
)

type Connection struct {

// type defines how host cluster will connect to host cluster
// ConnectionTypeDirect means direct connection, this requires
// kubeconfig and kubesphere apiserver endpoint provided
// ConnectionTypeProxy means using kubesphere proxy, no kubeconfig
// or kubesphere apiserver endpoint required
Type ConnectionType `json:"type,omitempty"`

// KubeSphere API Server endpoint. Example: http://10.10.0.11:8080
// Should provide this field explicitly if connection type is direct.
// Will be populated by ks-apiserver if connection type is proxy.
KubeSphereAPIEndpoint string `json:"kubesphereAPIEndpoint,omitempty"`

// Kubernetes API Server endpoint. Example: https://10.10.0.1:6443
// Should provide this field explicitly if connection type is direct.
// Will be populated by ks-apiserver if connection type is proxy.
KubernetesAPIEndpoint string `json:"kubernetesAPIEndpoint,omitempty"`

// External Kubernetes API Server endpoint
// Will be populated by ks-apiserver if connection type is proxy and ExternalKubeAPIEnabled is true.
ExternalKubernetesAPIEndpoint string `json:"externalKubernetesAPIEndpoint,omitempty"`

// KubeConfig content used to connect to cluster api server
// Should provide this field explicitly if connection type is direct.
// Will be populated by ks-proxy if connection type is proxy.
KubeConfig []byte `json:"kubeconfig,omitempty"`

// Token used by agents of member cluster to connect to host cluster proxy.
// This field is populated by apiserver only if connection type is proxy.
Token string `json:"token,omitempty"`

// KubeAPIServerPort is the port which listens for forwarding kube-apiserver traffic
// Only applicable when connection type is proxy.
KubernetesAPIServerPort uint16 `json:"kubernetesAPIServerPort,omitempty"`

// KubeSphereAPIServerPort is the port which listens for forwarding kubesphere apigateway traffic
// Only applicable when connection type is proxy.
KubeSphereAPIServerPort uint16 `json:"kubesphereAPIServerPort,omitempty"`
}
type MultiClusterStatus struct {

// Represents the latest available observations of a cluster's current state.
Conditions []ClusterCondition `json:"conditions,omitempty"`

// GitVersion of the kubernetes cluster, this field is populated by cluster controller
KubernetesVersion string `json:"kubernetesVersion,omitempty"`

// GitVersion of the /kapis/version api response, this field is populated by cluster controller
KubeSphereVersion string `json:"kubeSphereVersion,omitempty"`

// Count of the kubernetes cluster nodes
// This field may not reflect the instant status of the cluster.
NodeCount int `json:"nodeCount,omitempty"`

// Zones are the names of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'.
// +optional
Zones []string `json:"zones,omitempty"`

// Region is the name of the region in which all of the nodes in the cluster exist. e.g. 'us-east1'.
// +optional
Region *string `json:"region,omitempty"`

// Configz is status of components enabled in the member cluster. This is synchronized with member cluster
// every amount of time, like 5 minutes.
// +optional
Configz map[string]bool `json:"configz,omitempty"`

// UID is the kube-system namespace UID of the cluster, which represents the unique ID of the cluster.
UID types.UID `json:"uid,omitempty"`
}
type ClusterConditionType string

const (
// Cluster agent is initialized and waiting for connecting
ClusterInitialized ClusterConditionType = "Initialized"

// Cluster agent is available
ClusterAgentAvailable ClusterConditionType = "AgentAvailable"

// Cluster has been one of federated clusters
ClusterFederated ClusterConditionType = "Federated"

// Cluster external access ready
ClusterExternalAccessReady ClusterConditionType = "ExternalAccessReady"

// Cluster is all available for requests
ClusterReady ClusterConditionType = "Ready"

// Openpitrix runtime is created
ClusterOpenPitrixRuntimeReady ClusterConditionType = "OpenPitrixRuntimeReady"

// ClusterKubeConfigCertExpiresInSevenDays indicates that the cluster certificate is about to expire.
ClusterKubeConfigCertExpiresInSevenDays ClusterConditionType = "KubeConfigCertExpiresInSevenDays"
)

type ClusterCondition struct {
// Type of the condition
Type ClusterConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown.
Status corev1.ConditionStatus `json:"status"`
// The last time this condition was updated.
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
// Last time the condition transitioned from one status to another.
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// The reason for the condition's last transition.
Reason string `json:"reason,omitempty"`
// A human readable message indicating details about the transition.
Message string `json:"message,omitempty"`
}
type MultiCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MultiClusterSpec `json:"spec,omitempty"`
Status MultiClusterStatus `json:"status,omitempty"`
}

type MultiClusterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MultiCluster `json:"items"`
}

const HostClusterLabel = "cluster-role.kubesphere.io/host"

func (m MultiCluster) IsHostCluster() bool {
_, host := m.Labels[HostClusterLabel]
return host
}
18 changes: 18 additions & 0 deletions cmd/kk/cmd/clusterinfo/clusterinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package clusterinfo

import (
"github.com/spf13/cobra"
)

// NewCmdClusterInfo creates a new clusterinfo command
func NewCmdClusterInfo() *cobra.Command {

cmd := &cobra.Command{
Use: "cluster-info",
Short: "display cluster information",
}

cmd.AddCommand(NewCmdClusterInfoDump())

return cmd
}
60 changes: 60 additions & 0 deletions cmd/kk/cmd/clusterinfo/clusterinfo_dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package clusterinfo

import (
"fmt"
"github.com/kubesphere/kubekey/v3/cmd/kk/cmd/util"
"github.com/kubesphere/kubekey/v3/cmd/kk/pkg/clusterinfo"
"github.com/spf13/cobra"
)

type ClusterInfoDumpOptions struct {
Options clusterinfo.DumpOption
}

func NewClusterInfoDumpOptions() *ClusterInfoDumpOptions {
return &ClusterInfoDumpOptions{}
}

func NewCmdClusterInfoDump() *cobra.Command {
o := NewClusterInfoDumpOptions()
cmd := &cobra.Command{
Use: "dump",
Short: "Dumping key cluster configurations and files",
Run: func(cmd *cobra.Command, args []string) {

util.CheckErr(o.Validate())
util.CheckErr(o.Run())

},
}

o.addFlag(cmd)
return cmd
}

func (o *ClusterInfoDumpOptions) Validate() error {
switch o.Options.Type {
case "yaml", "YAML", "json", "JSON":
default:
return fmt.Errorf("unsupport output content format [%s]", o.Options.Type)
}
return nil
}

func (o *ClusterInfoDumpOptions) addFlag(cmd *cobra.Command) {
DefaultDumpNamespaces := []string{"kubesphere-system", "kubesphere-logging-system", "kubesphere-monitoring-system", "openpitrix-system", "kube-system", "istio-system", "kubesphere-devops-system", "porter-system"}
cmd.Flags().StringArrayVar(&o.Options.Namespace, "namespaces", DefaultDumpNamespaces, "Namespaces to be dumped, separated by commas.")
cmd.Flags().StringVar(&o.Options.KubeConfig, "kube-config", "", "Path to the kube-config file")
cmd.Flags().BoolVarP(&o.Options.AllNamespaces, "all-namespaces", "A", false, "dump all namespaces.")
cmd.Flags().StringVar(&o.Options.OutputDir, "output-dir", "", "output the dump result to the specified directory directory.")
cmd.Flags().StringVarP(&o.Options.Type, "output", "o", "json", "output file content format. support in json,yaml")
cmd.Flags().BoolVarP(&o.Options.Tar, "tar", "t", false, "build the dump result into a tar")
cmd.Flags().IntVar(&o.Options.Queue, "queue", 5, "dump queue size")
cmd.Flags().BoolVar(&o.Options.Logger, "log", false, "output the dump result to the log console")
}

func (o *ClusterInfoDumpOptions) Run() error {
fmt.Println("dumping cluster info...")
return clusterinfo.Dump(o.Options)

}
3 changes: 2 additions & 1 deletion cmd/kk/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cmd

import (
"fmt"
"github.com/kubesphere/kubekey/v3/cmd/kk/cmd/clusterinfo"
"os"
"os/exec"
"runtime"
Expand Down Expand Up @@ -120,7 +121,7 @@ func NewKubeKeyCommand(o KubeKeyOptions) *cobra.Command {

cmds.AddCommand(completion.NewCmdCompletion())
cmds.AddCommand(version.NewCmdVersion())

cmds.AddCommand(clusterinfo.NewCmdClusterInfo())
return cmds
}

Expand Down
121 changes: 121 additions & 0 deletions cmd/kk/pkg/clusterinfo/clusterinfo_dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package clusterinfo

import (
"bytes"
"encoding/json"
"fmt"
"github.com/kubesphere/kubekey/v3/cmd/kk/apis/kubekey/v1alpha2"
"github.com/kubesphere/kubekey/v3/cmd/kk/pkg/utils"
"github.com/xuri/excelize/v2"
)

type DumpOption struct {
Namespace []string
KubeConfig string
AllNamespaces bool
OutputDir string
Type string
Tar bool
Queue int
Logger bool
}

func Dump(option DumpOption) error {
client, err := utils.NewClient(option.KubeConfig)
if err != nil {
return err
}
dump := NewDumpOption(client)

cluster, err := dump.GetMultiCluster()
if err != nil {
return err
}

fileChan := NewFileChan(option, excelize.NewFile())
queue := make(chan struct{}, option.Queue)
go func() {
err = fileChan.WriteFile()
if err != nil {
fmt.Printf("failed to write file %s", err.Error())
fileChan.WaitGroup.Done()
}
}()

for _, multiCluster := range cluster {
fileChan.WaitGroup.Add(1)
queue <- struct{}{}
go func(multiCluster v1alpha2.MultiCluster) {
defer func() {
<-queue
}()
if multiCluster.IsHostCluster() {
fileChan.ReadData(dump, multiCluster.Name)
} else {
clusterClient, err := utils.NewClientForCluster(multiCluster.Spec.Connection.KubeConfig)
if err != nil {
fmt.Printf("failed to create cluster %s", multiCluster.Name)
fileChan.WaitGroup.Done()
return
}
fileChan.ReadData(NewDumpOption(clusterClient), multiCluster.Name)
}
}(multiCluster)
}

defer func() {
if err := fileChan.Excel.Close(); err != nil {
fmt.Println(err)
}
close(fileChan.OutFileChan)
close(queue)
}()

fileChan.WaitGroup.Wait()

fileChan.Excel.DeleteSheet("Sheet1")
fileChan.Excel.SaveAs(fmt.Sprintf("%s/%s", option.GetOutputDir(), "cluster_dump.xlsx"))

if option.Tar {
err = NewTar(option).Run()
if err != nil {
fmt.Printf("failed to tar file %s", err.Error())
return err
}
}

return nil
}

func resourcesClassification(resources interface{}) map[string][]interface{} {

var resourcesMap []map[string]interface{}
if marshal, err := json.Marshal(resources); err != nil {
fmt.Println(err, "marshal resources error")
return nil
} else {
decoder := json.NewDecoder(bytes.NewReader(marshal))
if err = decoder.Decode(&resourcesMap); err != nil {
fmt.Println(err, "Decode resources error")
return nil
}
}

var completeMap = make(map[string][]interface{})
for _, m := range resourcesMap {
namespace, ok := m["metadata"].(map[string]interface{})["namespace"]
if ok {
completeMap[namespace.(string)] = append(completeMap[namespace.(string)], m)
} else {
completeMap[""] = append(completeMap[""], m)
}
}
return completeMap
}

func (c *DumpOption) GetOutputDir() string {
if c.OutputDir == "" {
return "cluster_dump"
}
return c.OutputDir
}
Loading

0 comments on commit fa646fb

Please sign in to comment.