Merge pull request #114191 from apelisse/uds-pprof

Allow profiling information to be served on Unix-Domain Socket
This commit is contained in:
Kubernetes Prow Robot 2022-12-12 15:19:44 -08:00 committed by GitHub
commit 472612ccc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 0 deletions

View File

@ -126,6 +126,7 @@ type Config struct {
EnableIndex bool
EnableProfiling bool
DebugSocketPath string
EnableDiscovery bool
// Requires generic profiling enabled
@ -360,6 +361,7 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
EnableIndex: true,
EnableDiscovery: true,
EnableProfiling: true,
DebugSocketPath: "",
EnableMetrics: true,
MaxRequestsInFlight: 400,
MaxMutatingRequestsInFlight: 200,
@ -631,6 +633,11 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
return c.BuildHandlerChainFunc(handler, c.Config)
}
var debugSocket *routes.DebugSocket
if c.DebugSocketPath != "" {
debugSocket = routes.NewDebugSocket(c.DebugSocketPath)
}
apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
s := &GenericAPIServer{
@ -645,6 +652,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
EquivalentResourceRegistry: c.EquivalentResourceRegistry,
HandlerChainWaitGroup: c.HandlerChainWaitGroup,
Handler: apiServerHandler,
UnprotectedDebugSocket: debugSocket,
listedPathProvider: apiServerHandler,
@ -914,6 +922,13 @@ func installAPI(s *GenericAPIServer, c *Config) {
// so far, only logging related endpoints are considered valid to add for these debug flags.
routes.DebugFlags{}.Install(s.Handler.NonGoRestfulMux, "v", routes.StringFlagPutHandler(logs.GlogSetter))
}
if s.UnprotectedDebugSocket != nil {
s.UnprotectedDebugSocket.InstallProfiling()
s.UnprotectedDebugSocket.InstallDebugFlag("v", routes.StringFlagPutHandler(logs.GlogSetter))
if c.EnableContentionProfiling {
goruntime.SetBlockProfileRate(1)
}
}
if c.EnableMetrics {
if c.EnableProfiling {

View File

@ -32,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
"k8s.io/apimachinery/pkg/version"
@ -136,6 +137,10 @@ type GenericAPIServer struct {
// Handler holds the handlers being used by this API server
Handler *APIServerHandler
// UnprotectedDebugSocket is used to serve pprof information in a unix-domain socket. This socket is
// not protected by authentication/authorization.
UnprotectedDebugSocket *routes.DebugSocket
// listedPathProvider is a lister which provides the set of paths to show at /
listedPathProvider routes.ListedPathProvider
@ -467,6 +472,14 @@ func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
// Clean up resources on shutdown.
defer s.Destroy()
// If UDS profiling is enabled, start a local http server listening on that socket
if s.UnprotectedDebugSocket != nil {
go func() {
defer utilruntime.HandleCrash()
klog.Error(s.UnprotectedDebugSocket.Run(stopCh))
}()
}
// spawn a new goroutine for closing the MuxAndDiscoveryComplete signal
// registration happens during construction of the generic api server
// the last server in the chain aggregates signals from the previous instances

View File

@ -25,6 +25,7 @@ import (
type FeatureOptions struct {
EnableProfiling bool
DebugSocketPath string
EnableContentionProfiling bool
}
@ -33,6 +34,7 @@ func NewFeatureOptions() *FeatureOptions {
return &FeatureOptions{
EnableProfiling: defaults.EnableProfiling,
DebugSocketPath: defaults.DebugSocketPath,
EnableContentionProfiling: defaults.EnableContentionProfiling,
}
}
@ -46,6 +48,8 @@ func (o *FeatureOptions) AddFlags(fs *pflag.FlagSet) {
"Enable profiling via web interface host:port/debug/pprof/")
fs.BoolVar(&o.EnableContentionProfiling, "contention-profiling", o.EnableContentionProfiling,
"Enable lock contention profiling, if profiling is enabled")
fs.StringVar(&o.DebugSocketPath, "debug-socket-path", o.DebugSocketPath,
"Use an unprotected (no authn/authz) unix-domain socket for profiling with the given path")
}
func (o *FeatureOptions) ApplyTo(c *server.Config) error {
@ -54,6 +58,7 @@ func (o *FeatureOptions) ApplyTo(c *server.Config) error {
}
c.EnableProfiling = o.EnableProfiling
c.DebugSocketPath = o.DebugSocketPath
c.EnableContentionProfiling = o.EnableContentionProfiling
return nil

View File

@ -0,0 +1,82 @@
/*
Copyright 2022 The Kubernetes 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 routes
import (
"fmt"
"net"
"net/http"
"net/http/pprof"
"os"
"path"
)
// DebugSocket installs profiling and debugflag as a Unix-Domain socket.
type DebugSocket struct {
path string
mux *http.ServeMux
}
// NewDebugSocket creates a new DebugSocket for the given path.
func NewDebugSocket(path string) *DebugSocket {
return &DebugSocket{
path: path,
mux: http.NewServeMux(),
}
}
// InstallProfiling installs profiling endpoints in the socket.
func (s *DebugSocket) InstallProfiling() {
s.mux.HandleFunc("/debug/pprof", redirectTo("/debug/pprof/"))
s.mux.HandleFunc("/debug/pprof/", pprof.Index)
s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
s.mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
// InstallDebugFlag installs debug flag endpoints in the socket.
func (s *DebugSocket) InstallDebugFlag(flag string, handler func(http.ResponseWriter, *http.Request)) {
f := DebugFlags{}
s.mux.HandleFunc("/debug/flags", f.Index)
s.mux.HandleFunc("/debug/flags/", f.Index)
url := path.Join("/debug/flags", flag)
s.mux.HandleFunc(url, handler)
f.addFlag(flag)
}
// Run starts the server and waits for stopCh to be closed to close the server.
func (s *DebugSocket) Run(stopCh <-chan struct{}) error {
if err := os.Remove(s.path); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove (%v): %v", s.path, err)
}
l, err := net.Listen("unix", s.path)
if err != nil {
return fmt.Errorf("listen error (%v): %v", s.path, err)
}
defer l.Close()
srv := http.Server{Handler: s.mux}
go func() {
<-stopCh
srv.Close()
}()
return srv.Serve(l)
}