mirror of
https://github.com/alibaba/higress.git
synced 2026-02-27 14:10:51 +08:00
feat: add debug mode for higress (#753)
Signed-off-by: sjcsjc123 <1401189096@qq.com>
This commit is contained in:
338
pkg/cmd/hgctl/code_debug.go
Normal file
338
pkg/cmd/hgctl/code_debug.go
Normal file
@@ -0,0 +1,338 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// 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 hgctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultIp = "127.0.0.1"
|
||||
DefaultPort = ":15051"
|
||||
)
|
||||
|
||||
func newCodeDebugCmd() *cobra.Command {
|
||||
codeDebugCmd := &cobra.Command{
|
||||
Use: "code-debug",
|
||||
Short: "Start or stop code debug",
|
||||
}
|
||||
|
||||
codeDebugCmd.AddCommand(getStartCodeDebugCmd())
|
||||
codeDebugCmd.AddCommand(getStopCodeDebugCmd())
|
||||
|
||||
return codeDebugCmd
|
||||
}
|
||||
|
||||
func getStartCodeDebugCmd() *cobra.Command {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Printf("fail to get user home dir: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
kubeConfigDir := homeDir + "/.kube/config"
|
||||
|
||||
startCodeDebugCmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Aliases: []string{"start"},
|
||||
Short: "Start code debug",
|
||||
Example: "hgctl code-debug start",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
writer := c.OutOrStdout()
|
||||
|
||||
// wait for user to confirm
|
||||
if !promptCodeDebug(writer, "local grpc address") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check profile type is local or not
|
||||
fmt.Fprintf(writer, "Checking profile type...\n")
|
||||
profiles, err := getAllProfiles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get all profiles: %v", err)
|
||||
}
|
||||
if len(profiles) == 0 {
|
||||
fmt.Fprintf(writer, "Higress hasn't been installed yet!\n")
|
||||
return nil
|
||||
}
|
||||
for _, profile := range profiles {
|
||||
if profile.Install != helm.InstallLocalK8s {
|
||||
fmt.Fprintf(writer, "\nHigress needs to be installed locally!\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// get kubernetes clientSet
|
||||
fmt.Fprintf(writer, "Getting kubernetes clientset...\n")
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to build config from kubeconfig: %v", err)
|
||||
return nil
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to create kubernetes clientset: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// get non-loopback IPv4 address
|
||||
fmt.Fprintf(writer, "Getting non-loopback IPv4 address...\n")
|
||||
ip, err := getNonLoopbackIPv4()
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to get non-loopback IPv4 address: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// update the xds address in higress-config ConfigMap
|
||||
// and trigger rollout for higress-controller and higress-gateway deployments
|
||||
fmt.Fprintf(writer, "Updating xds address in higress-config ConfigMap "+
|
||||
"and triggering rollout for higress-controller and higress-gateway deployments...\n")
|
||||
err = updateXdsIpAndRollout(clientSet, ip, DefaultPort)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to update xds address in higress-config ConfigMap: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "Code debug started!\n")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
startCodeDebugCmd.PersistentFlags().StringVar(&kubeConfigDir, "kubeconfig", kubeConfigDir,
|
||||
"Use a Kubernetes configuration file instead of in-cluster configuration")
|
||||
|
||||
return startCodeDebugCmd
|
||||
}
|
||||
|
||||
func getStopCodeDebugCmd() *cobra.Command {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Printf("fail to get user home dir: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
kubeConfigDir := homeDir + "/.kube/config"
|
||||
|
||||
stopCodeDebugCmd := &cobra.Command{
|
||||
Use: "stop",
|
||||
Aliases: []string{"stop"},
|
||||
Short: "Stop code debug",
|
||||
Example: "hgctl code-debug stop",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
// wait for user to confirm
|
||||
writer := c.OutOrStdout()
|
||||
if !promptCodeDebug(writer, "default grpc address") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check profile type is local or not
|
||||
fmt.Fprintf(writer, "Checking profile type...\n")
|
||||
profiles, err := getAllProfiles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get all profiles: %v", err)
|
||||
}
|
||||
if len(profiles) == 0 {
|
||||
fmt.Fprintf(writer, "Higress hasn't been installed yet!\n")
|
||||
return nil
|
||||
}
|
||||
for _, profile := range profiles {
|
||||
if profile.Install != helm.InstallLocalK8s {
|
||||
fmt.Fprintf(writer, "\nHigress needs to be installed locally!\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// get kubernetes clientSet
|
||||
fmt.Fprintf(writer, "Getting kubernetes clientset...\n")
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to build config from kubeconfig: %v", err)
|
||||
return nil
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to create kubernetes clientset: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// recover the xds address in higress-config ConfigMap
|
||||
// and trigger rollout for higress-controller and higress-gateway deployments
|
||||
fmt.Fprintf(writer, "Recovering xds address in higress-config ConfigMap "+
|
||||
"and triggering rollout for higress-controller and higress-gateway deployments...\n")
|
||||
err = updateXdsIpAndRollout(clientSet, DefaultIp, DefaultPort)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "fail to recover xds address in higress-config ConfigMap: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "Code debug stopped!\n")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
stopCodeDebugCmd.PersistentFlags().StringVar(&kubeConfigDir, "kubeconfig", kubeConfigDir,
|
||||
"Use a Kubernetes configuration file instead of in-cluster configuration")
|
||||
|
||||
return stopCodeDebugCmd
|
||||
}
|
||||
|
||||
// getNonLoopbackIPv4 returns the first non-loopback IPv4 address of the host.
|
||||
func getNonLoopbackIPv4() (string, error) {
|
||||
// get all network interfaces
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// traverse all network interfaces
|
||||
for _, i := range interfaces {
|
||||
// exclude loopback interface and virtual interface
|
||||
if i.Flags&net.FlagLoopback == 0 && i.Flags&net.FlagUp != 0 {
|
||||
// get all addresses of the interface
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// traverse all addresses of the interface
|
||||
for _, addr := range addrs {
|
||||
// check the type of the address is IP address
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
// check the IP address is IPv4 address
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Non-loopback IPv4 address not found")
|
||||
}
|
||||
|
||||
// updateXdsIpAndRollout updates the xds address in higress-config ConfigMap
|
||||
// and triggers rollout for higress-controller and higress-gateway deployments
|
||||
// also can recover the xds address in higress-config ConfigMap
|
||||
func updateXdsIpAndRollout(c *kubernetes.Clientset, ip string, port string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Get higress-config ConfigMap
|
||||
cm, err := c.CoreV1().ConfigMaps("higress-system").Get(ctx, "higress-config", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update mesh field in higress-config ConfigMap
|
||||
if _, ok := cm.Data["mesh"]; !ok {
|
||||
return fmt.Errorf("mesh not found in configmap higress-config")
|
||||
}
|
||||
mesh := cm.Data["mesh"]
|
||||
newMesh, err := replaceXDSAddress(mesh, ip, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data["mesh"] = newMesh
|
||||
|
||||
// Update higress-config ConfigMap
|
||||
_, err = c.CoreV1().ConfigMaps("higress-system").Update(ctx, cm, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Trigger rollout for higress-controller deployment
|
||||
err = triggerRollout(c, "higress-controller")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Trigger rollout for higress-gateway deployment
|
||||
err = triggerRollout(c, "higress-gateway")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// triggerRollout triggers rollout for the specified deployment
|
||||
func triggerRollout(clientset *kubernetes.Clientset, deploymentName string) error {
|
||||
deploymentsClient := clientset.AppsV1().Deployments("higress-system")
|
||||
|
||||
// Get the deployment
|
||||
deployment, err := deploymentsClient.Get(context.TODO(), deploymentName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Increment the deployment's revision to trigger a rollout
|
||||
deployment.Spec.Template.ObjectMeta.Labels["version"] = time.Now().Format("20060102150405")
|
||||
|
||||
// Update the deployment
|
||||
_, err = deploymentsClient.Update(context.TODO(), deployment, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// replaceXDSAddress replaces the xds address in the config string with new IP and Port
|
||||
func replaceXDSAddress(configString, newIP, newPort string) (string, error) {
|
||||
// define the regular expression to match xds address
|
||||
xdsRegex := regexp.MustCompile(`xds://[0-9.:]+`)
|
||||
|
||||
// find the first match
|
||||
match := xdsRegex.FindString(configString)
|
||||
if match == "" {
|
||||
// if no match, return error
|
||||
return "", fmt.Errorf("no xds address found in config string")
|
||||
}
|
||||
|
||||
// replace xds address with new IP and Port
|
||||
newXDSAddress := fmt.Sprintf("xds://%s%s", newIP, newPort)
|
||||
result := xdsRegex.ReplaceAllString(configString, newXDSAddress)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// promptCodeDebug prompts user to confirm code debug
|
||||
func promptCodeDebug(writer io.Writer, t string) bool {
|
||||
answer := ""
|
||||
for {
|
||||
fmt.Fprintf(writer, "This will start set xds address to %s in higress-config ConfigMap "+
|
||||
"and trigger rollout for higress-controller and higress-gateway deployments. \nProceed? (y/N)", t)
|
||||
fmt.Scanln(&answer)
|
||||
if answer == "y" {
|
||||
return true
|
||||
}
|
||||
if answer == "N" {
|
||||
fmt.Fprintf(writer, "Cancelled.\n")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user