mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-11 10:23:17 +08:00
Three related cleanups in cmd/{ateapi,atelet,ateom-gvisor}/main.go:
1. atelet Run/Checkpoint/Restore handlers shared three blocks of
cut-and-paste (fetchRunsc + MkdirAll, errgroup over pause + application
containers calling prepareOCIDirectory, dial-ateom + NewAteomClient, the
ateletpb -> ateompb WorkloadSpec projection). Factored into
fetchRunscAndPrep, prepareOCIBundles, dialAteom, buildAteomWorkloadSpec,
plus a uploadIfExists for the optional pages.img / pages_meta.img upload
in Checkpoint.
2. ateapi main() was a 242-line wall of inlined startup steps;
decomposed into named helpers (loadFlagsFromEnv, logFlagValues,
connectRedis with buildRedisTLSConfig + pingRedisWithRetries,
newKubeClients, buildServerCreds) so the top-level flow reads as
orchestration.
3. ateapi, atelet, and ateom-gvisor each carried their own ~30-line
initTracing + ~35-line initMetrics + slog/metrics-server boilerplate
that differed only in ServiceName, sampler choice, and whether the OTLP
exporter was wired. Lifted to a new internal/serverboot package exposing
InitLogger, InitTracing (with TracingOptions.NoExporter for ateom-gvisor
which loses egress once eth0 moves into the gvisor netns), InitMetrics,
Fatal, ShutdownProvider, and StartMetricsServer.
No behaviour change. Verified with go vet, GOOS=linux go build, and go
test ./cmd/ateapi/internal/...
Fixes #<issue_number_goes_here>
> It's a good idea to open an issue first for discussion.
- [ ] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
Signed-off-by: Davanum Srinivas <davanum@gmail.com>
168 lines
5.9 KiB
Go
168 lines
5.9 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// 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 serverboot collects the startup boilerplate shared by the
|
|
// long-running substrate server binaries (ateapi, atelet, ateom-gvisor):
|
|
// slog wiring, OTel tracer + meter providers, a Prometheus + /readyz
|
|
// HTTP surface, and a couple of small helpers for startup fail-fast.
|
|
package serverboot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/agent-substrate/substrate/internal/contextlogging"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
"go.opentelemetry.io/otel/exporters/prometheus"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
|
)
|
|
|
|
// InitLogger sets the global slog logger to a JSON handler wrapped in
|
|
// contextlogging.NewHandler. Call once at process start.
|
|
func InitLogger() {
|
|
slog.SetDefault(slog.New(contextlogging.NewHandler(slog.NewJSONHandler(os.Stdout, nil))))
|
|
}
|
|
|
|
// TracingOptions configures InitTracing.
|
|
type TracingOptions struct {
|
|
// ServiceName is required; populates resource.semconv ServiceName.
|
|
ServiceName string
|
|
// Sampler is required. ateapi typically uses ParentBased(AlwaysSample);
|
|
// atelet/ateom-gvisor use ParentBased(NeverSample).
|
|
Sampler sdktrace.Sampler
|
|
// NoExporter skips the OTLP exporter. Used by binaries with no
|
|
// network egress (ateom-gvisor, after eth0 moves into the gvisor
|
|
// netns).
|
|
NoExporter bool
|
|
}
|
|
|
|
// InitTracing registers a global TracerProvider with the given options
|
|
// and the TraceContext text-map propagator.
|
|
func InitTracing(ctx context.Context, opts TracingOptions) (*sdktrace.TracerProvider, error) {
|
|
if opts.ServiceName == "" {
|
|
return nil, fmt.Errorf("TracingOptions.ServiceName is required")
|
|
}
|
|
res, err := resource.New(ctx,
|
|
resource.WithAttributes(semconv.ServiceName(opts.ServiceName)),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create tracer resource: %w", err)
|
|
}
|
|
|
|
tpOpts := []sdktrace.TracerProviderOption{
|
|
sdktrace.WithResource(res),
|
|
sdktrace.WithSampler(opts.Sampler),
|
|
}
|
|
if !opts.NoExporter {
|
|
exporter, err := otlptracegrpc.New(ctx,
|
|
// GKE managed traces doesn't support validating the TLS certs of the collector.
|
|
otlptracegrpc.WithInsecure(),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create OTLP exporter: %w", err)
|
|
}
|
|
tpOpts = append(tpOpts, sdktrace.WithBatcher(exporter))
|
|
}
|
|
|
|
tp := sdktrace.NewTracerProvider(tpOpts...)
|
|
otel.SetTracerProvider(tp)
|
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
|
return tp, nil
|
|
}
|
|
|
|
// InitMetrics registers a global MeterProvider with both a Prometheus
|
|
// reader (exposed via StartMetricsServer's /metrics handler) and an
|
|
// OTLP periodic reader.
|
|
func InitMetrics(ctx context.Context, serviceName string) (*sdkmetric.MeterProvider, error) {
|
|
if serviceName == "" {
|
|
return nil, fmt.Errorf("serviceName is required")
|
|
}
|
|
promExporter, err := prometheus.New()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create Prometheus metric exporter: %w", err)
|
|
}
|
|
otlpExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithInsecure())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create OTLP metric exporter: %w", err)
|
|
}
|
|
res, err := resource.New(ctx,
|
|
resource.WithAttributes(semconv.ServiceName(serviceName)),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create metric resource: %w", err)
|
|
}
|
|
mp := sdkmetric.NewMeterProvider(
|
|
sdkmetric.WithReader(promExporter),
|
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(otlpExporter)),
|
|
sdkmetric.WithResource(res),
|
|
)
|
|
otel.SetMeterProvider(mp)
|
|
return mp, nil
|
|
}
|
|
|
|
// Fatal logs msg + err and exits with status 1. For startup-time
|
|
// fail-fast where there's no recovery path.
|
|
func Fatal(ctx context.Context, msg string, err error) {
|
|
slog.ErrorContext(ctx, msg, slog.Any("err", err))
|
|
os.Exit(1)
|
|
}
|
|
|
|
// ShutdownProvider invokes the OTel provider's Shutdown and logs any
|
|
// error. Designed to be deferred from main():
|
|
//
|
|
// defer serverboot.ShutdownProvider("TracerProvider", tp.Shutdown)
|
|
func ShutdownProvider(name string, shutdown func(context.Context) error) {
|
|
if err := shutdown(context.Background()); err != nil {
|
|
slog.Error("Failed to shutdown "+name, slog.Any("err", err))
|
|
}
|
|
}
|
|
|
|
// MetricsServerOptions configures StartMetricsServer.
|
|
type MetricsServerOptions struct {
|
|
// Addr is the TCP listen address (e.g. ":9090").
|
|
Addr string
|
|
// EnableReadyz adds a /readyz handler that returns 200 OK. Some
|
|
// binaries (ateapi) want it for Kubernetes readiness probes; others
|
|
// (atelet) historically didn't surface one.
|
|
EnableReadyz bool
|
|
}
|
|
|
|
// StartMetricsServer runs an HTTP server exposing /metrics (Prometheus)
|
|
// and optionally /readyz. Blocks until http.ListenAndServe returns;
|
|
// designed to be `go`-launched.
|
|
func StartMetricsServer(ctx context.Context, opts MetricsServerOptions) {
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/metrics", promhttp.Handler())
|
|
if opts.EnableReadyz {
|
|
mux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("ok"))
|
|
})
|
|
}
|
|
slog.InfoContext(ctx, fmt.Sprintf("Starting Prometheus metrics server on %s", opts.Addr))
|
|
if err := http.ListenAndServe(opts.Addr, mux); err != nil {
|
|
slog.Error("Failed to start prometheus metrics server", slog.Any("err", err))
|
|
}
|
|
}
|