-
Notifications
You must be signed in to change notification settings - Fork 935
feat(coderd/healthcheck): add health check for proxy #10846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a17ec4c
49b6e19
c7e202c
89bae7e
bcdc08c
340a276
d3478c8
c746021
05d4b09
56be002
1112b1c
fed35bd
7f8760a
5f5c486
b9cbdd8
ae30089
21f52bb
79dc190
2530320
f192488
b45e9c5
63fbafb
6de94bc
b57cdeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,17 +13,19 @@ import ( | |
) | ||
|
||
const ( | ||
SectionDERP string = "DERP" | ||
SectionAccessURL string = "AccessURL" | ||
SectionWebsocket string = "Websocket" | ||
SectionDatabase string = "Database" | ||
SectionDERP string = "DERP" | ||
SectionAccessURL string = "AccessURL" | ||
SectionWebsocket string = "Websocket" | ||
SectionDatabase string = "Database" | ||
SectionWorkspaceProxy string = "WorkspaceProxy" | ||
) | ||
|
||
type Checker interface { | ||
DERP(ctx context.Context, opts *derphealth.ReportOptions) derphealth.Report | ||
AccessURL(ctx context.Context, opts *AccessURLReportOptions) AccessURLReport | ||
Websocket(ctx context.Context, opts *WebsocketReportOptions) WebsocketReport | ||
Database(ctx context.Context, opts *DatabaseReportOptions) DatabaseReport | ||
WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) WorkspaceProxyReport | ||
} | ||
|
||
// @typescript-generate Report | ||
|
@@ -38,20 +40,22 @@ type Report struct { | |
// FailingSections is a list of sections that have failed their healthcheck. | ||
FailingSections []string `json:"failing_sections"` | ||
|
||
DERP derphealth.Report `json:"derp"` | ||
AccessURL AccessURLReport `json:"access_url"` | ||
Websocket WebsocketReport `json:"websocket"` | ||
Database DatabaseReport `json:"database"` | ||
DERP derphealth.Report `json:"derp"` | ||
AccessURL AccessURLReport `json:"access_url"` | ||
Websocket WebsocketReport `json:"websocket"` | ||
Database DatabaseReport `json:"database"` | ||
WorkspaceProxy WorkspaceProxyReport `json:"workspace_proxy"` | ||
|
||
// The Coder version of the server that the report was generated on. | ||
CoderVersion string `json:"coder_version"` | ||
} | ||
|
||
type ReportOptions struct { | ||
AccessURL AccessURLReportOptions | ||
Database DatabaseReportOptions | ||
DerpHealth derphealth.ReportOptions | ||
Websocket WebsocketReportOptions | ||
AccessURL AccessURLReportOptions | ||
Database DatabaseReportOptions | ||
DerpHealth derphealth.ReportOptions | ||
Websocket WebsocketReportOptions | ||
WorkspaceProxy WorkspaceProxyReportOptions | ||
|
||
Checker Checker | ||
} | ||
|
@@ -78,6 +82,11 @@ func (defaultChecker) Database(ctx context.Context, opts *DatabaseReportOptions) | |
return report | ||
} | ||
|
||
func (defaultChecker) WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) (report WorkspaceProxyReport) { | ||
report.Run(ctx, opts) | ||
return report | ||
} | ||
|
||
func Run(ctx context.Context, opts *ReportOptions) *Report { | ||
var ( | ||
wg sync.WaitGroup | ||
|
@@ -136,6 +145,18 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { | |
report.Database = opts.Checker.Database(ctx, &opts.Database) | ||
}() | ||
|
||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
defer func() { | ||
if err := recover(); err != nil { | ||
report.WorkspaceProxy.Error = ptr.Ref(fmt.Sprint(err)) | ||
} | ||
}() | ||
|
||
report.WorkspaceProxy = opts.Checker.Works 8000 paceProxy(ctx, &opts.WorkspaceProxy) | ||
}() | ||
|
||
report.CoderVersion = buildinfo.Version() | ||
wg.Wait() | ||
|
||
|
@@ -153,6 +174,9 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { | |
if !report.Database.Healthy { | ||
report.FailingSections = append(report.FailingSections, SectionDatabase) | ||
} | ||
if !report.WorkspaceProxy.Healthy { | ||
report.FailingSections = append(report.FailingSections, SectionWorkspaceProxy) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side thought: frontend does not use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Will file a follow-up PR Edit: #10854 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's beneficial to have a structure that can be navigated in a generic way. for (let section of report.failing_sections) {
console.log(report[section].reason);
} Given a good design, it will "always works", even when new fields and structures are added. Granted, I'm not a proponent for the current design, just for the ability to do so. |
||
} | ||
|
||
report.Healthy = len(report.FailingSections) == 0 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package healthcheck | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"cdr.dev/slog" | ||
"github.com/coder/coder/v2/buildinfo" | ||
"github.com/coder/coder/v2/coderd/util/ptr" | ||
"github.com/coder/coder/v2/codersdk" | ||
) | ||
|
||
type WorkspaceProxyReportOptions struct { | ||
// UpdateProxyHealth is a function called when healthcheck is run. | ||
// This would normally be ProxyHealth.ForceUpdate(). | ||
// We do this because if someone mashes the healthcheck refresh button | ||
// they would expect up-to-date data. | ||
UpdateProxyHealth func(context.Context) error | ||
// FetchWorkspaceProxies is a function that returns the available workspace proxies. | ||
FetchWorkspaceProxies func(context.Context) (codersdk.RegionsResponse[codersdk.WorkspaceProxy], error) | ||
// CurrentVersion is the current server version. | ||
// We pass this in to make it easier to test. | ||
CurrentVersion string | ||
Logger slog.Logger | ||
} | ||
|
||
// @typescript-generate Report | ||
type WorkspaceProxyReport struct { | ||
Healthy bool `json:"healthy"` | ||
Warnings []string `json:"warnings"` | ||
Error *string `json:"error"` | ||
|
||
WorkspaceProxies codersdk.RegionsResponse[codersdk.WorkspaceProxy] | ||
} | ||
|
||
func (r *WorkspaceProxyReport) Run(ctx context.Context, opts *WorkspaceProxyReportOptions) { | ||
r.Healthy = true | ||
r.Warnings = []string{} | ||
|
||
if opts.FetchWorkspaceProxies == nil { | ||
opts.Logger.Debug(ctx, "no workspace proxies configured") | ||
return | ||
} | ||
|
||
if opts.UpdateProxyHealth == nil { | ||
err := "opts.UpdateProxyHealth must not be nil if opts.FetchWorkspaceProxies is not nil" | ||
opts.Logger.Error(ctx, "developer error: "+err) | ||
r.Error = ptr.Ref(err) | ||
return | ||
} | ||
|
||
if err := opts.UpdateProxyHealth(ctx); err != nil { | ||
opts.Logger.Error(ctx, "failed to update proxy health: %w", err) | ||
r.Error = ptr.Ref(err.Error()) | ||
return | ||
} | ||
|
||
proxies, err := opts.FetchWorkspaceProxies(ctx) | ||
if err != nil { | ||
opts.Logger.Error(ctx, "failed to fetch workspace proxies", slog.Error(err)) | ||
r.Healthy = false | ||
r.Error = ptr.Ref(err.Error()) | ||
return | ||
} | ||
|
||
r.WorkspaceProxies = proxies | ||
|
||
var numProxies int | ||
var healthyProxies int | ||
for _, proxy := range r.WorkspaceProxies.Regions { | ||
numProxies++ | ||
if proxy.Healthy { | ||
healthyProxies++ | ||
} | ||
|
||
// check versions | ||
if !buildinfo.VersionsMatch(proxy.Version, opts.CurrentVersion) { | ||
opts.Logger.Warn(ctx, "proxy version mismatch", | ||
slog.F("version", opts.CurrentVersion), | ||
slog.F("proxy_version", proxy.Version), | ||
slog.F("proxy_name", proxy.Name), | ||
) | ||
r.Healthy = false | ||
r.Warnings = append(r.Warnings, fmt.Sprintf("Proxy %q version %q does not match primary server version %q", proxy.Name, proxy.Version, opts.CurrentVersion)) | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.