Skip to content

Commit 7d7adca

Browse files
authored
fixed registry bug with debug config (#722)
1 parent 0a3d27b commit 7d7adca

4 files changed

Lines changed: 90 additions & 4 deletions

File tree

cmd/hauler/cli/store/serve.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ func ServeRegistryCmd(ctx context.Context, o *flags.ServeRegistryOpts, s *store.
126126
return err
127127
}
128128

129+
if cfg.HTTP.Debug.Addr != "" {
130+
l.Infof("starting debug server on address [%s]", cfg.HTTP.Debug.Addr)
131+
if cfg.HTTP.Debug.Prometheus.Enabled {
132+
path := cfg.HTTP.Debug.Prometheus.Path
133+
if path == "" {
134+
path = "/metrics"
135+
}
136+
l.Infof("providing prometheus metrics on [%s]", path)
137+
}
138+
}
139+
server.ConfigureDebugServer(cfg)
140+
129141
if err = r.ListenAndServe(); err != nil {
130142
return err
131143
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/containerd/errdefs v1.0.0
99
github.com/distribution/distribution/v3 v3.1.1
1010
github.com/distribution/reference v0.6.0
11+
github.com/docker/go-metrics v0.0.1
1112
github.com/dustin/go-humanize v1.0.1
1213
github.com/google/go-containerregistry v0.21.9
1314
github.com/google/uuid v1.6.0
@@ -132,7 +133,6 @@ require (
132133
github.com/docker/docker-credential-helpers v0.9.5 // indirect
133134
github.com/docker/go-connections v0.7.0 // indirect
134135
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
135-
github.com/docker/go-metrics v0.0.1 // indirect
136136
github.com/docker/go-units v0.5.0 // indirect
137137
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
138138
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect

internal/server/registry.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/distribution/distribution/v3/configuration"
1212
"github.com/distribution/distribution/v3/registry"
1313
"github.com/distribution/distribution/v3/registry/handlers"
14+
dockermetrics "github.com/docker/go-metrics"
1415
"github.com/pkg/errors"
1516
"github.com/sirupsen/logrus"
1617
)
@@ -24,6 +25,27 @@ func NewRegistry(ctx context.Context, cfg *configuration.Configuration) (*regist
2425
return r, nil
2526
}
2627

28+
// ConfigureDebugServer starts pprof/expvar/prometheus on cfg.HTTP.Debug.Addr
29+
func ConfigureDebugServer(cfg *configuration.Configuration) {
30+
if cfg.HTTP.Debug.Addr == "" {
31+
return
32+
}
33+
34+
if cfg.HTTP.Debug.Prometheus.Enabled {
35+
path := cfg.HTTP.Debug.Prometheus.Path
36+
if path == "" {
37+
path = "/metrics"
38+
}
39+
http.Handle(path, dockermetrics.Handler())
40+
}
41+
42+
go func(addr string) {
43+
if err := http.ListenAndServe(addr, nil); err != nil { //nolint:gosec // debug interface: internal-only, no read/write timeouts needed
44+
logrus.Fatalf("error listening on debug interface: %v", err)
45+
}
46+
}(cfg.HTTP.Debug.Addr)
47+
}
48+
2749
type tmpRegistryServer struct {
2850
*httptest.Server
2951
}

internal/server/server_test.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package server
22

33
import (
44
"context"
5+
"io"
6+
"net"
57
"net/http"
68
"strings"
79
"testing"
10+
"time"
811

12+
"github.com/distribution/distribution/v3/configuration"
913
// Register the filesystem storage driver for the distribution registry.
1014
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
1115

@@ -16,9 +20,8 @@ func TestNewTempRegistry_StartStop(t *testing.T) {
1620
ctx := context.Background()
1721
srv := NewTempRegistry(ctx, t.TempDir())
1822

19-
// Start the httptest server directly to avoid the Start() method's
20-
// retry logic which only accepts HTTP 200, while /v2 returns 401
21-
// from the distribution registry.
23+
// start the httptest server directly to avoid the retry logic which only accepts HTTP 200
24+
// while /v2 returns 401 from the distribution registry.
2225
srv.Server.Start()
2326
t.Cleanup(func() { srv.Stop() })
2427

@@ -74,6 +77,55 @@ func TestNewFile_Configuration(t *testing.T) {
7477
}
7578
}
7679

80+
// this is the only test in the package allowed to enable prometheus, since it registers
81+
// on http.DefaultServeMux and a second registration would panic.
82+
func TestConfigureDebugServer_Prometheus(t *testing.T) {
83+
// grab a free port and release it so ConfigureDebugServer can bind it
84+
l, err := net.Listen("tcp", "127.0.0.1:0")
85+
if err != nil {
86+
t.Fatalf("failed to reserve a free port: %v", err)
87+
}
88+
addr := l.Addr().String()
89+
l.Close()
90+
91+
cfg := &configuration.Configuration{}
92+
cfg.HTTP.Debug.Addr = addr
93+
cfg.HTTP.Debug.Prometheus.Enabled = true
94+
cfg.HTTP.Debug.Prometheus.Path = "/metrics"
95+
96+
ConfigureDebugServer(cfg)
97+
98+
var resp *http.Response
99+
for i := 0; i < 20; i++ {
100+
resp, err = http.Get("http://" + addr + "/metrics")
101+
if err == nil {
102+
break
103+
}
104+
time.Sleep(50 * time.Millisecond)
105+
}
106+
if err != nil {
107+
t.Fatalf("expected GET /metrics to eventually succeed, got error: %v", err)
108+
}
109+
defer resp.Body.Close()
110+
111+
if resp.StatusCode != http.StatusOK {
112+
t.Fatalf("expected status 200 from the prometheus handler, got %d", resp.StatusCode)
113+
}
114+
115+
body, err := io.ReadAll(resp.Body)
116+
if err != nil {
117+
t.Fatalf("failed to read response body: %v", err)
118+
}
119+
if !strings.Contains(string(body), "go_gc_duration_seconds") {
120+
t.Fatalf("expected prometheus-formatted metrics output, got: %s", body)
121+
}
122+
}
123+
124+
// an empty Debug.Addr should just no-op, not start a listener.
125+
func TestConfigureDebugServer_NoAddr(t *testing.T) {
126+
ConfigureDebugServer(&configuration.Configuration{})
127+
}
128+
77129
func TestNewFile_DefaultPort(t *testing.T) {
78130
ctx := context.Background()
79131
opts := flags.ServeFilesOpts{

0 commit comments

Comments
 (0)