fixed registry bug with debug config (#722)

This commit is contained in:
Zack Brady
2026-08-10 12:30:08 -04:00
committed by GitHub
parent 0a3d27b241
commit 7d7adcaf17
4 changed files with 90 additions and 4 deletions
+12
View File
@@ -126,6 +126,18 @@ func ServeRegistryCmd(ctx context.Context, o *flags.ServeRegistryOpts, s *store.
return err
}
if cfg.HTTP.Debug.Addr != "" {
l.Infof("starting debug server on address [%s]", cfg.HTTP.Debug.Addr)
if cfg.HTTP.Debug.Prometheus.Enabled {
path := cfg.HTTP.Debug.Prometheus.Path
if path == "" {
path = "/metrics"
}
l.Infof("providing prometheus metrics on [%s]", path)
}
}
server.ConfigureDebugServer(cfg)
if err = r.ListenAndServe(); err != nil {
return err
}
+1 -1
View File
@@ -8,6 +8,7 @@ require (
github.com/containerd/errdefs v1.0.0
github.com/distribution/distribution/v3 v3.1.1
github.com/distribution/reference v0.6.0
github.com/docker/go-metrics v0.0.1
github.com/dustin/go-humanize v1.0.1
github.com/google/go-containerregistry v0.21.9
github.com/google/uuid v1.6.0
@@ -132,7 +133,6 @@ require (
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
+22
View File
@@ -11,6 +11,7 @@ import (
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
"github.com/distribution/distribution/v3/registry/handlers"
dockermetrics "github.com/docker/go-metrics"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -24,6 +25,27 @@ func NewRegistry(ctx context.Context, cfg *configuration.Configuration) (*regist
return r, nil
}
// ConfigureDebugServer starts pprof/expvar/prometheus on cfg.HTTP.Debug.Addr
func ConfigureDebugServer(cfg *configuration.Configuration) {
if cfg.HTTP.Debug.Addr == "" {
return
}
if cfg.HTTP.Debug.Prometheus.Enabled {
path := cfg.HTTP.Debug.Prometheus.Path
if path == "" {
path = "/metrics"
}
http.Handle(path, dockermetrics.Handler())
}
go func(addr string) {
if err := http.ListenAndServe(addr, nil); err != nil { //nolint:gosec // debug interface: internal-only, no read/write timeouts needed
logrus.Fatalf("error listening on debug interface: %v", err)
}
}(cfg.HTTP.Debug.Addr)
}
type tmpRegistryServer struct {
*httptest.Server
}
+55 -3
View File
@@ -2,10 +2,14 @@ package server
import (
"context"
"io"
"net"
"net/http"
"strings"
"testing"
"time"
"github.com/distribution/distribution/v3/configuration"
// Register the filesystem storage driver for the distribution registry.
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
@@ -16,9 +20,8 @@ func TestNewTempRegistry_StartStop(t *testing.T) {
ctx := context.Background()
srv := NewTempRegistry(ctx, t.TempDir())
// Start the httptest server directly to avoid the Start() method's
// retry logic which only accepts HTTP 200, while /v2 returns 401
// from the distribution registry.
// start the httptest server directly to avoid the retry logic which only accepts HTTP 200
// while /v2 returns 401 from the distribution registry.
srv.Server.Start()
t.Cleanup(func() { srv.Stop() })
@@ -74,6 +77,55 @@ func TestNewFile_Configuration(t *testing.T) {
}
}
// this is the only test in the package allowed to enable prometheus, since it registers
// on http.DefaultServeMux and a second registration would panic.
func TestConfigureDebugServer_Prometheus(t *testing.T) {
// grab a free port and release it so ConfigureDebugServer can bind it
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to reserve a free port: %v", err)
}
addr := l.Addr().String()
l.Close()
cfg := &configuration.Configuration{}
cfg.HTTP.Debug.Addr = addr
cfg.HTTP.Debug.Prometheus.Enabled = true
cfg.HTTP.Debug.Prometheus.Path = "/metrics"
ConfigureDebugServer(cfg)
var resp *http.Response
for i := 0; i < 20; i++ {
resp, err = http.Get("http://" + addr + "/metrics")
if err == nil {
break
}
time.Sleep(50 * time.Millisecond)
}
if err != nil {
t.Fatalf("expected GET /metrics to eventually succeed, got error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status 200 from the prometheus handler, got %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if !strings.Contains(string(body), "go_gc_duration_seconds") {
t.Fatalf("expected prometheus-formatted metrics output, got: %s", body)
}
}
// an empty Debug.Addr should just no-op, not start a listener.
func TestConfigureDebugServer_NoAddr(t *testing.T) {
ConfigureDebugServer(&configuration.Configuration{})
}
func TestNewFile_DefaultPort(t *testing.T) {
ctx := context.Background()
opts := flags.ServeFilesOpts{