fix: allow coderd to start with an empty DERP map when built-in DERP is disabled (#24544)

Allow coderd to start with an empty base DERP map when built-in DERP
is disabled and no static DERP map is configured, so DERP can come from
workspace proxies after startup.

Also add a DERP healthcheck warning when no DERP servers are currently
available at runtime.

Related to: https://linear.app/codercom/issue/PLAT-43/bug-coderd-unable-to-be-started-if-built-in-derp-server-disabled-and
Related to: https://github.com/coder/coder/issues/22324
This commit is contained in:
George K
2026-04-28 09:17:08 -07:00
committed by GitHub
parent 1926b7e658
commit 3f0e015fe5
13 changed files with 125 additions and 70 deletions
+16
View File
@@ -34,6 +34,7 @@ const (
oneNodeUnhealthy = "Region is operational, but performance might be degraded as one node is unhealthy."
missingNodeReport = "Missing node health report, probably a developer error."
noSTUN = "No STUN servers are available."
noDERP = "No DERP servers are available."
stunMapVaryDest = "STUN returned different addresses; you may be behind a hard NAT."
)
@@ -69,11 +70,20 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
r.Regions = map[int]*healthsdk.DERPRegionReport{}
// Track whether the map contains any DERP nodes so we can warn if
// it does not.
hasDERP := false
wg := &sync.WaitGroup{}
mu := sync.Mutex{}
wg.Add(len(opts.DERPMap.Regions))
for _, region := range opts.DERPMap.Regions {
for _, node := range region.Nodes {
if !node.STUNOnly {
hasDERP = true
break
}
}
var (
region = region
regionReport = RegionReport{
@@ -103,6 +113,12 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
mu.Unlock()
}()
}
if !hasDERP {
r.Severity = health.SeverityWarning
r.Warnings = append(r.Warnings, health.Messagef(
health.CodeDERPNoNodes, noDERP,
))
}
ncLogf := func(format string, args ...interface{}) {
mu.Lock()
+37 -5
View File
@@ -64,6 +64,9 @@ func TestDERP(t *testing.T) {
report.Run(ctx, opts)
assert.True(t, report.Healthy)
for _, warning := range report.Warnings {
assert.NotEqual(t, health.CodeDERPNoNodes, warning.Code)
}
for _, region := range report.Regions {
assert.True(t, region.Healthy)
for _, node := range region.NodeReports {
@@ -361,7 +364,7 @@ func TestDERP(t *testing.T) {
}
})
t.Run("STUNOnly/OK", func(t *testing.T) {
t.Run("STUNOnly/WarnsNoDERP", func(t *testing.T) {
t.Parallel()
var (
@@ -389,7 +392,9 @@ func TestDERP(t *testing.T) {
report.Run(ctx, opts)
assert.True(t, report.Healthy)
assert.Equal(t, health.SeverityOK, report.Severity)
assert.Equal(t, health.SeverityWarning, report.Severity)
require.Len(t, report.Warnings, 1)
assert.Equal(t, health.CodeDERPNoNodes, report.Warnings[0].Code)
for _, region := range report.Regions {
assert.True(t, region.Healthy)
assert.Equal(t, health.SeverityOK, region.Severity)
@@ -405,6 +410,27 @@ func TestDERP(t *testing.T) {
}
})
t.Run("NoDERP/EmptyMap", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
report = derphealth.Report{}
opts = &derphealth.ReportOptions{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{},
},
}
)
report.Run(ctx, opts)
assert.Equal(t, health.SeverityWarning, report.Severity)
require.Len(t, report.Warnings, 1)
assert.Equal(t, health.CodeDERPNoNodes, report.Warnings[0].Code)
assert.Empty(t, report.Regions)
})
t.Run("STUNOnly/OneBadOneGood", func(t *testing.T) {
t.Parallel()
@@ -443,9 +469,15 @@ func TestDERP(t *testing.T) {
report.Run(ctx, opts)
assert.True(t, report.Healthy)
assert.Equal(t, health.SeverityWarning, report.Severity)
if assert.Len(t, report.Warnings, 1) {
assert.Equal(t, health.CodeDERPOneNodeUnhealthy, report.Warnings[0].Code)
}
assert.Len(t, report.Warnings, 2)
assert.Contains(t, []health.Code{
report.Warnings[0].Code,
report.Warnings[1].Code,
}, health.CodeDERPOneNodeUnhealthy)
assert.Contains(t, []health.Code{
report.Warnings[0].Code,
report.Warnings[1].Code,
}, health.CodeDERPNoNodes)
for _, region := range report.Regions {
assert.True(t, region.Healthy)
assert.Equal(t, health.SeverityWarning, region.Severity)
+1
View File
@@ -36,6 +36,7 @@ const (
CodeDERPNodeUsesWebsocket Code = `EDERP01`
CodeDERPOneNodeUnhealthy Code = `EDERP02`
CodeDERPNoNodes Code = `EDERP03`
CodeSTUNNoNodes = `ESTUN01`
CodeSTUNMapVaryDest = `ESTUN02`