From aea67fc8db9a82cb65e3b46676b0c6872943f54e Mon Sep 17 00:00:00 2001 From: Prashantkumar Khatri Date: Mon, 29 Jun 2026 22:20:15 +0530 Subject: [PATCH 1/2] feat: add pprof profiling support to interlink, virtual-kubelet, and ssh-tunnel components Signed-off-by: Prashantkumar Khatri --- cmd/interlink/main.go | 11 +++++++ cmd/ssh-tunnel/main.go | 23 ++++++++++++++ cmd/virtual-kubelet/main.go | 21 +++++++++++++ pkg/interlink/config.go | 24 +++++++++++++++ pkg/pprof/pprof.go | 48 +++++++++++++++++++++++++++++ pkg/pprof/pprof_test.go | 60 ++++++++++++++++++++++++++++++++++++ pkg/virtualkubelet/config.go | 12 ++++++++ 7 files changed, 199 insertions(+) create mode 100644 pkg/pprof/pprof.go create mode 100644 pkg/pprof/pprof_test.go diff --git a/cmd/interlink/main.go b/cmd/interlink/main.go index a373723a..acb29825 100644 --- a/cmd/interlink/main.go +++ b/cmd/interlink/main.go @@ -22,6 +22,7 @@ import ( "github.com/interlink-hq/interlink/pkg/interlink" "github.com/interlink-hq/interlink/pkg/interlink/api" + ilpprof "github.com/interlink-hq/interlink/pkg/pprof" "github.com/interlink-hq/interlink/pkg/virtualkubelet" "k8s.io/cri-client/pkg/util" ) @@ -112,6 +113,16 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + pprofAddr := interLinkConfig.Pprof.Address + if pprofAddr == "" { + pprofAddr = "127.0.0.1" + } + pprofPort := interLinkConfig.Pprof.Port + if pprofPort == "" { + pprofPort = "6061" + } + ilpprof.Start(ctx, interLinkConfig.Pprof.Enabled, pprofAddr+":"+pprofPort, "127.0.0.1:6061") + if os.Getenv("ENABLE_TRACING") == "1" { shutdown, err := interlink.InitTracer(ctx, "InterLink-Plugin-") if err != nil { diff --git a/cmd/ssh-tunnel/main.go b/cmd/ssh-tunnel/main.go index 0880beb5..58f6aaf0 100644 --- a/cmd/ssh-tunnel/main.go +++ b/cmd/ssh-tunnel/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/base64" "flag" "fmt" @@ -9,6 +10,7 @@ import ( "net" "os" + ilpprof "github.com/interlink-hq/interlink/pkg/pprof" "golang.org/x/crypto/ssh" ) @@ -63,8 +65,29 @@ func main() { remotePort := flag.String("rport", "", "remote port for tunnel") localSocket := flag.String("lsock", "", "local socket for tunnel") hostkeyFile := flag.String("hostkeyfile", "", "file with public key for SSH host check") + pprofEnabled := flag.Bool("pprof", false, "enable pprof profiling") + pprofAddr := flag.String("pprof-address", "127.0.0.1", "pprof listen address") + pprofPort := flag.String("pprof-port", "6062", "pprof listen port") flag.Parse() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + enabled := *pprofEnabled + if os.Getenv("ENABLE_PPROF") == "true" { + enabled = true + } + pAddr := *pprofAddr + if os.Getenv("PPROF_ADDRESS") != "" { + pAddr = os.Getenv("PPROF_ADDRESS") + } + pPort := *pprofPort + if os.Getenv("PPROF_PORT") != "" { + pPort = os.Getenv("PPROF_PORT") + } + + ilpprof.Start(ctx, enabled, pAddr+":"+pPort, "127.0.0.1:6062") + var hostKeyCallback ssh.HostKeyCallback if *hostkeyFile == "" { diff --git a/cmd/virtual-kubelet/main.go b/cmd/virtual-kubelet/main.go index 6aa26587..c3dc0011 100644 --- a/cmd/virtual-kubelet/main.go +++ b/cmd/virtual-kubelet/main.go @@ -83,6 +83,7 @@ import ( "k8s.io/client-go/informers" "github.com/interlink-hq/interlink/pkg/interlink" + ilpprof "github.com/interlink-hq/interlink/pkg/pprof" commonIL "github.com/interlink-hq/interlink/pkg/virtualkubelet" ) @@ -464,6 +465,26 @@ func main() { } setupLogging(interLinkConfig) + + pprofEnabled := vkConfig.Pprof.Enabled + if os.Getenv("ENABLE_PPROF") == "true" { + pprofEnabled = true + } + pprofAddr := vkConfig.Pprof.Address + if os.Getenv("PPROF_ADDRESS") != "" { + pprofAddr = os.Getenv("PPROF_ADDRESS") + } + if pprofAddr == "" { + pprofAddr = "127.0.0.1" + } + pprofPort := vkConfig.Pprof.Port + if os.Getenv("PPROF_PORT") != "" { + pprofPort = os.Getenv("PPROF_PORT") + } + if pprofPort == "" { + pprofPort = "6060" + } + ilpprof.Start(ctx, pprofEnabled, pprofAddr+":"+pprofPort, "127.0.0.1:6060") log.G(ctx).Info("Config dump", interLinkConfig) shutdownTracing := setupTracing(ctx) diff --git a/pkg/interlink/config.go b/pkg/interlink/config.go index 032b6c77..a63119b6 100644 --- a/pkg/interlink/config.go +++ b/pkg/interlink/config.go @@ -116,6 +116,18 @@ type Config struct { DataRootFolder string `yaml:"DataRootFolder"` // TLS contains TLS/mTLS configuration for secure communication TLS TLSConfig `yaml:"TLS,omitempty"` + // Pprof contains configuration for the pprof profiling server + Pprof PprofConfig `yaml:"Pprof,omitempty"` +} + +// PprofConfig holds configuration for the pprof profiling server. +type PprofConfig struct { + // Enabled indicates whether the pprof server is enabled + Enabled bool `yaml:"Enabled"` + // Address is the listen address for pprof server (default: 127.0.0.1) + Address string `yaml:"Address,omitempty"` + // Port is the listen port for pprof server (default: 6061) + Port string `yaml:"Port,omitempty"` } // TLSConfig holds TLS/mTLS configuration for secure communication. @@ -373,5 +385,17 @@ func NewInterLinkConfig() (Config, error) { interLinkNewConfig.Sidecarport = os.Getenv("SIDECARPORT") } + if os.Getenv("ENABLE_PPROF") == "true" { + interLinkNewConfig.Pprof.Enabled = true + } + + if os.Getenv("PPROF_PORT") != "" { + interLinkNewConfig.Pprof.Port = os.Getenv("PPROF_PORT") + } + + if os.Getenv("PPROF_ADDRESS") != "" { + interLinkNewConfig.Pprof.Address = os.Getenv("PPROF_ADDRESS") + } + return interLinkNewConfig, nil } diff --git a/pkg/pprof/pprof.go b/pkg/pprof/pprof.go new file mode 100644 index 00000000..647ae0ab --- /dev/null +++ b/pkg/pprof/pprof.go @@ -0,0 +1,48 @@ +package pprof + +import ( + "context" + "net" + "net/http" + _ "net/http/pprof" + + "github.com/sirupsen/logrus" +) + +// Start starts a pprof HTTP server in a background goroutine if enabled. +// It listens on listenAddr, falling back to defaultAddr if listenAddr is empty. +// The server stops when the provided context is canceled. +func Start(ctx context.Context, enabled bool, listenAddr string, defaultAddr string) { + if !enabled { + return + } + + addr := listenAddr + if addr == "" { + addr = defaultAddr + } + + if _, _, err := net.SplitHostPort(addr); err != nil { + if !net.ParseIP(addr).IsLoopback() && !net.ParseIP(addr).IsUnspecified() { + addr = ":" + addr + } + } + + logrus.Infof("Starting pprof server on http://%s/debug/pprof/", addr) + + server := &http.Server{ + Addr: addr, + } + + go func() { + <-ctx.Done() + logrus.Infof("Shutting down pprof server on %s", addr) + _ = server.Close() + }() + + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logrus.Errorf("pprof server on %s failed: %v", addr, err) + } + }() +} diff --git a/pkg/pprof/pprof_test.go b/pkg/pprof/pprof_test.go new file mode 100644 index 00000000..f79e4399 --- /dev/null +++ b/pkg/pprof/pprof_test.go @@ -0,0 +1,60 @@ +package pprof + +import ( + "context" + "fmt" + "net" + "net/http" + "testing" + "time" +) + +func TestPprofStart(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Failed to find a free port: %v", err) + } + addr := listener.Addr().String() + listener.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + Start(ctx, true, addr, "127.0.0.1:6060") + + time.Sleep(100 * time.Millisecond) + + url := fmt.Sprintf("http://%s/debug/pprof/", addr) + resp, err := http.Get(url) + if err != nil { + t.Fatalf("Failed to request pprof endpoint: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status 200 OK, got %d", resp.StatusCode) + } +} + +func TestPprofDisabled(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Failed to find a free port: %v", err) + } + addr := listener.Addr().String() + listener.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + Start(ctx, false, addr, "127.0.0.1:6060") + + time.Sleep(100 * time.Millisecond) + + // Verify that the server is NOT listening/responding + url := fmt.Sprintf("http://%s/debug/pprof/", addr) + _, err = http.Get(url) + if err == nil { + t.Error("Expected connection failure for disabled pprof server, but request succeeded") + } +} diff --git a/pkg/virtualkubelet/config.go b/pkg/virtualkubelet/config.go index b3ed38ff..cdfd7ae6 100644 --- a/pkg/virtualkubelet/config.go +++ b/pkg/virtualkubelet/config.go @@ -58,6 +58,8 @@ type Config struct { SkipDownwardAPIResolution bool `yaml:"SkipDownwardAPIResolution,omitempty"` // DisableCSR disables CSR (CertificateSigningRequest) creation and uses self-signed certificates instead DisableCSR bool `yaml:"DisableCSR,omitempty"` + // Pprof configures the pprof profiling server + Pprof PprofConfig `yaml:"Pprof,omitempty"` } // TLSConfig holds TLS/mTLS configuration for secure communication with interLink API. @@ -72,6 +74,16 @@ type TLSConfig struct { CACertFile string `yaml:"CACertFile,omitempty"` } +// PprofConfig holds configuration for the pprof profiling server. +type PprofConfig struct { + // Enabled indicates whether the pprof server is enabled + Enabled bool `yaml:"Enabled"` + // Address is the listen address for pprof server (default: 127.0.0.1) + Address string `yaml:"Address,omitempty"` + // Port is the listen port for pprof server (default: 6060) + Port string `yaml:"Port,omitempty"` +} + // HTTP defines security settings for HTTP connections. // It determines whether connections are insecure and holds CA certificates. type HTTP struct { From 31b8d507f40d4805e70a7c7cdbf8ed196e969c5d Mon Sep 17 00:00:00 2001 From: Prashantkumar Khatri Date: Mon, 29 Jun 2026 23:01:57 +0530 Subject: [PATCH 2/2] fix: improve pprof reliability with retried tests, graceful shutdown, and updated address parsing Signed-off-by: Prashantkumar Khatri --- cmd/ssh-tunnel/main.go | 10 ++++----- pkg/pprof/pprof.go | 18 ++++++++++++--- pkg/pprof/pprof_test.go | 49 +++++++++++++++++++++++------------------ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/cmd/ssh-tunnel/main.go b/cmd/ssh-tunnel/main.go index 58f6aaf0..84e5fdbc 100644 --- a/cmd/ssh-tunnel/main.go +++ b/cmd/ssh-tunnel/main.go @@ -70,9 +70,6 @@ func main() { pprofPort := flag.String("pprof-port", "6062", "pprof listen port") flag.Parse() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - enabled := *pprofEnabled if os.Getenv("ENABLE_PPROF") == "true" { enabled = true @@ -86,8 +83,6 @@ func main() { pPort = os.Getenv("PPROF_PORT") } - ilpprof.Start(ctx, enabled, pAddr+":"+pPort, "127.0.0.1:6062") - var hostKeyCallback ssh.HostKeyCallback if *hostkeyFile == "" { @@ -117,6 +112,11 @@ func main() { if err != nil { log.Fatalf("unable to parse private key: %v", err) } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ilpprof.Start(ctx, enabled, pAddr+":"+pPort, "127.0.0.1:6062") + // An SSH client is represented with a ClientConn. // // To authenticate with the remote server you must pass at least one diff --git a/pkg/pprof/pprof.go b/pkg/pprof/pprof.go index 647ae0ab..acf49a3e 100644 --- a/pkg/pprof/pprof.go +++ b/pkg/pprof/pprof.go @@ -4,7 +4,8 @@ import ( "context" "net" "net/http" - _ "net/http/pprof" + _ "net/http/pprof" //nolint:gosec // G108: profiling endpoint exposure is intentional + "time" "github.com/sirupsen/logrus" ) @@ -23,7 +24,14 @@ func Start(ctx context.Context, enabled bool, listenAddr string, defaultAddr str } if _, _, err := net.SplitHostPort(addr); err != nil { - if !net.ParseIP(addr).IsLoopback() && !net.ParseIP(addr).IsUnspecified() { + isPortOnly := true + for _, r := range addr { + if r < '0' || r > '9' { + isPortOnly = false + break + } + } + if isPortOnly { addr = ":" + addr } } @@ -37,7 +45,11 @@ func Start(ctx context.Context, enabled bool, listenAddr string, defaultAddr str go func() { <-ctx.Done() logrus.Infof("Shutting down pprof server on %s", addr) - _ = server.Close() + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(shutdownCtx); err != nil { + logrus.Errorf("pprof server on %s shutdown error: %v", addr, err) + } }() go func() { diff --git a/pkg/pprof/pprof_test.go b/pkg/pprof/pprof_test.go index f79e4399..3e5efcc0 100644 --- a/pkg/pprof/pprof_test.go +++ b/pkg/pprof/pprof_test.go @@ -9,52 +9,57 @@ import ( "time" ) -func TestPprofStart(t *testing.T) { - listener, err := net.Listen("tcp", "127.0.0.1:0") +func freeAddr(t *testing.T) string { + t.Helper() + l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatalf("Failed to find a free port: %v", err) + t.Fatalf("failed to find a free port: %v", err) } - addr := listener.Addr().String() - listener.Close() + addr := l.Addr().String() + l.Close() + return addr +} + +func TestPprofStart(t *testing.T) { + addr := freeAddr(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() Start(ctx, true, addr, "127.0.0.1:6060") - time.Sleep(100 * time.Millisecond) - + client := &http.Client{Timeout: 500 * time.Millisecond} url := fmt.Sprintf("http://%s/debug/pprof/", addr) - resp, err := http.Get(url) + var resp *http.Response + var err error + for i := 0; i < 10; i++ { + resp, err = client.Get(url) + if err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } if err != nil { - t.Fatalf("Failed to request pprof endpoint: %v", err) + t.Fatalf("pprof endpoint did not become ready: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status 200 OK, got %d", resp.StatusCode) + t.Errorf("expected status 200 OK, got %d", resp.StatusCode) } } func TestPprofDisabled(t *testing.T) { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Failed to find a free port: %v", err) - } - addr := listener.Addr().String() - listener.Close() + addr := freeAddr(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() Start(ctx, false, addr, "127.0.0.1:6060") - time.Sleep(100 * time.Millisecond) - - // Verify that the server is NOT listening/responding - url := fmt.Sprintf("http://%s/debug/pprof/", addr) - _, err = http.Get(url) + conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond) if err == nil { - t.Error("Expected connection failure for disabled pprof server, but request succeeded") + _ = conn.Close() + t.Error("expected connection failure for disabled pprof server, but dial succeeded") } }