runsc metric-server: Add --pid-file flag for PID file.

This causes the metric server to write its own PID to the given file.
The file is deleted when the metric server exits (cleanly).

Doing so is useful for the usual reasons PID files are useful for, i.e.
identification by other systems that the metric server is running, and knowing
where to send signals.

The PID is also available over HTTP at `/runsc-metrics/pid`.

PiperOrigin-RevId: 512747410
This commit is contained in:
Etienne Perot
2023-02-27 15:27:59 -08:00
committed by gVisor bot
parent 6d9c0872cf
commit 8035cf9ed5
+34 -4
View File
@@ -233,6 +233,8 @@ func queryMetrics(ctx context.Context, sand *sandbox.Sandbox, verifier *promethe
// MetricServer implements subcommands.Command for the "metric-server" command.
type MetricServer struct {
rootDir string
pid int
pidFile string
allowUnknownRoot bool
exposeProfileEndpoints bool
address string
@@ -296,6 +298,7 @@ func (*MetricServer) Usage() string {
// SetFlags implements subcommands.Command.SetFlags.
func (m *MetricServer) SetFlags(f *flag.FlagSet) {
f.StringVar(&m.pidFile, "pid-file", "", "If set, write the metric server's own PID to this file after binding to the --metric-server address. The parent directory of this file must already exist.")
f.BoolVar(&m.exposeProfileEndpoints, "allow-profiling", false, "If true, expose /runsc-metrics/profile-cpu and /runsc-metrics/profile-heap to get profiling data about the metric server")
}
@@ -724,7 +727,7 @@ func (m *MetricServer) serveHealthCheck(w http.ResponseWriter, req *http.Request
m.mu.Lock()
defer m.mu.Unlock()
if m.shuttingDown {
return httpResult{http.StatusServiceUnavailable, errors.New("server is shutting down already")}
return httpResult{http.StatusServiceUnavailable, errors.New("server is shutting down")}
}
if err := req.ParseForm(); err != nil {
return httpResult{http.StatusBadRequest, err}
@@ -738,6 +741,17 @@ func (m *MetricServer) serveHealthCheck(w http.ResponseWriter, req *http.Request
return httpOK
}
// servePID serves the PID of the metric server process.
func (m *MetricServer) servePID(w http.ResponseWriter, req *http.Request) httpResult {
m.mu.Lock()
defer m.mu.Unlock()
if m.shuttingDown {
return httpResult{http.StatusServiceUnavailable, errors.New("server is shutting down")}
}
io.WriteString(w, strconv.Itoa(m.pid))
return httpOK
}
// profileCPU returns a CPU profile over HTTP.
func (m *MetricServer) profileCPU(w http.ResponseWriter, req *http.Request) httpResult {
// Time to finish up profiling and flush out the results to the client.
@@ -806,6 +820,11 @@ func (m *MetricServer) shutdownLocked(ctx context.Context) {
m.udsPath = ""
}
}
if m.pidFile != "" {
if err := os.Remove(m.pidFile); err != nil {
log.Warningf("Cannot remove PID file at %s: %v", m.pidFile, err)
}
}
m.srv.Shutdown(ctx)
}
@@ -907,6 +926,9 @@ func (m *MetricServer) Execute(ctx context.Context, f *flag.FlagSet, args ...any
m.address = conf.MetricServer
m.sandboxes = make(map[container.FullID]*servedSandbox)
m.lastStateFileStat = make(map[container.FullID]os.FileInfo)
m.pid = os.Getpid()
m.shutdownCh = make(chan os.Signal, 1)
signal.Notify(m.shutdownCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
var listener net.Listener
var listenErr error
@@ -951,6 +973,7 @@ func (m *MetricServer) Execute(ctx context.Context, f *flag.FlagSet, args ...any
mux := http.NewServeMux()
mux.HandleFunc("/runsc-metrics/healthcheck", logRequest(m.serveHealthCheck))
mux.HandleFunc("/runsc-metrics/pid", logRequest(m.servePID))
if m.exposeProfileEndpoints {
log.Warningf("Profiling HTTP endpoints are exposed; this should only be used for development!")
mux.HandleFunc("/runsc-metrics/profile-cpu", logRequest(m.profileCPU))
@@ -961,11 +984,18 @@ func (m *MetricServer) Execute(ctx context.Context, f *flag.FlagSet, args ...any
m.srv.Handler = mux
m.srv.ReadTimeout = httpTimeout
m.srv.WriteTimeout = httpTimeout
m.shutdownCh = make(chan os.Signal, 1)
go m.verifyLoop(ctx)
if m.pidFile != "" {
if err := ioutil.WriteFile(m.pidFile, []byte(fmt.Sprintf("%d", m.pid)), 0644); err != nil {
return util.Errorf("Cannot write PID to file %q: %v", m.pidFile, err)
}
defer os.Remove(m.pidFile)
log.Infof("Wrote PID %d to file %v.", m.pid, m.pidFile)
}
// Initialization complete.
log.Infof("Server serving on %s for root directory %s.", conf.MetricServer, conf.RootDir)
go m.verifyLoop(ctx)
signal.Notify(m.shutdownCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
serveErr := m.srv.Serve(listener)
log.Infof("Server has stopped accepting requests.")
m.mu.Lock()