mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 07:32:57 +08:00

* feat: add ability to launch targeted dskit modules in the grafana server CLI command This commit adds a ModuleServer and ModuleRunner suitable for launching dskit services and updates the server cli command to use this instead of the full Server. The default behavior is unchanged and will launch the full Grafana server. Individual services are targeted by setting target=comma,seperated,list in the config file. * require dev mode to target dskit modules * remove unused type * replace setting.CommandLineArgs w/setting.Cfg; the caller can deal with calling setting.NewCfg * Update pkg/server/module_server.go Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com> --------- Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
206 lines
5.3 KiB
Go
206 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/grafana/dskit/services"
|
|
|
|
"github.com/grafana/grafana/pkg/api"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/modules"
|
|
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
// NewModule returns an instance of a ModuleServer, responsible for managing
|
|
// dskit modules (services).
|
|
func NewModule(opts Options, apiOpts api.ServerOptions, cfg *setting.Cfg) (*ModuleServer, error) {
|
|
s, err := newModuleServer(opts, apiOpts, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.init(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func newModuleServer(opts Options, apiOpts api.ServerOptions, cfg *setting.Cfg) (*ModuleServer, error) {
|
|
rootCtx, shutdownFn := context.WithCancel(context.Background())
|
|
|
|
s := &ModuleServer{
|
|
opts: opts,
|
|
apiOpts: apiOpts,
|
|
context: rootCtx,
|
|
shutdownFn: shutdownFn,
|
|
shutdownFinished: make(chan struct{}),
|
|
log: log.New("base-server"),
|
|
cfg: cfg,
|
|
pidFile: opts.PidFile,
|
|
version: opts.Version,
|
|
commit: opts.Commit,
|
|
buildBranch: opts.BuildBranch,
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// ModuleServer is responsible for managing the lifecycle of dskit services. The
|
|
// ModuleServer has the minimal set of dependencies to launch dskit services,
|
|
// but it can be used to launch the entire Grafana server.
|
|
type ModuleServer struct {
|
|
opts Options
|
|
apiOpts api.ServerOptions
|
|
|
|
context context.Context
|
|
shutdownFn context.CancelFunc
|
|
log log.Logger
|
|
cfg *setting.Cfg
|
|
shutdownOnce sync.Once
|
|
shutdownFinished chan struct{}
|
|
isInitialized bool
|
|
mtx sync.Mutex
|
|
|
|
pidFile string
|
|
version string
|
|
commit string
|
|
buildBranch string
|
|
}
|
|
|
|
// init initializes the server and its services.
|
|
func (s *ModuleServer) init() error {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
|
|
if s.isInitialized {
|
|
return nil
|
|
}
|
|
s.isInitialized = true
|
|
|
|
if err := s.writePIDFile(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Run initializes and starts services. This will block until all services have
|
|
// exited. To initiate shutdown, call the Shutdown method in another goroutine.
|
|
func (s *ModuleServer) Run() error {
|
|
defer close(s.shutdownFinished)
|
|
|
|
if err := s.init(); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.notifySystemd("READY=1")
|
|
s.log.Debug("Waiting on services...")
|
|
|
|
// Only allow individual dskit modules to run in dev mode.
|
|
if s.cfg.Env != "dev" {
|
|
if len(s.cfg.Target) > 1 || s.cfg.Target[0] != "all" {
|
|
s.log.Error("dskit module targeting is only supported in dev mode. Falling back to 'all'")
|
|
s.cfg.Target = []string{"all"}
|
|
}
|
|
}
|
|
|
|
m := modules.New(s.cfg.Target)
|
|
|
|
m.RegisterModule(modules.Core, func() (services.Service, error) {
|
|
return NewService(s.cfg, s.opts, s.apiOpts)
|
|
})
|
|
|
|
m.RegisterModule(modules.GrafanaAPIServer, func() (services.Service, error) {
|
|
return grafanaapiserver.New(path.Join(s.cfg.DataPath, "k8s"))
|
|
})
|
|
|
|
m.RegisterModule(modules.All, nil)
|
|
|
|
return m.Run(s.context)
|
|
}
|
|
|
|
// Shutdown initiates Grafana graceful shutdown. This shuts down all
|
|
// running background services. Since Run blocks Shutdown supposed to
|
|
// be run from a separate goroutine.
|
|
func (s *ModuleServer) Shutdown(ctx context.Context, reason string) error {
|
|
var err error
|
|
s.shutdownOnce.Do(func() {
|
|
s.log.Info("Shutdown started", "reason", reason)
|
|
// Call cancel func to stop background services.
|
|
s.shutdownFn()
|
|
// Wait for server to shut down
|
|
select {
|
|
case <-s.shutdownFinished:
|
|
s.log.Debug("Finished waiting for server to shut down")
|
|
case <-ctx.Done():
|
|
s.log.Warn("Timed out while waiting for server to shut down")
|
|
err = fmt.Errorf("timeout waiting for shutdown")
|
|
}
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// writePIDFile retrieves the current process ID and writes it to file.
|
|
func (s *ModuleServer) writePIDFile() error {
|
|
if s.pidFile == "" {
|
|
return nil
|
|
}
|
|
|
|
// Ensure the required directory structure exists.
|
|
err := os.MkdirAll(filepath.Dir(s.pidFile), 0700)
|
|
if err != nil {
|
|
s.log.Error("Failed to verify pid directory", "error", err)
|
|
return fmt.Errorf("failed to verify pid directory: %s", err)
|
|
}
|
|
|
|
// Retrieve the PID and write it to file.
|
|
pid := strconv.Itoa(os.Getpid())
|
|
if err := os.WriteFile(s.pidFile, []byte(pid), 0644); err != nil {
|
|
s.log.Error("Failed to write pidfile", "error", err)
|
|
return fmt.Errorf("failed to write pidfile: %s", err)
|
|
}
|
|
|
|
s.log.Info("Writing PID file", "path", s.pidFile, "pid", pid)
|
|
return nil
|
|
}
|
|
|
|
// notifySystemd sends state notifications to systemd.
|
|
func (s *ModuleServer) notifySystemd(state string) {
|
|
notifySocket := os.Getenv("NOTIFY_SOCKET")
|
|
if notifySocket == "" {
|
|
s.log.Debug(
|
|
"NOTIFY_SOCKET environment variable empty or unset, can't send systemd notification")
|
|
return
|
|
}
|
|
|
|
socketAddr := &net.UnixAddr{
|
|
Name: notifySocket,
|
|
Net: "unixgram",
|
|
}
|
|
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
|
|
if err != nil {
|
|
s.log.Warn("Failed to connect to systemd", "err", err, "socket", notifySocket)
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
s.log.Warn("Failed to close connection", "err", err)
|
|
}
|
|
}()
|
|
|
|
_, err = conn.Write([]byte(state))
|
|
if err != nil {
|
|
s.log.Warn("Failed to write notification to systemd", "err", err)
|
|
}
|
|
}
|