mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-23 01:11:40 +00:00

Ensure no services are advertised as part of shutting down tailscaled. Prefs are only edited if services are currently advertised, and they're edited we wait for control's ~15s (+ buffer) delay to failover. Note that editing prefs will trigger a synchronous write to the state Secret, so it may fail to persist state if the ProxyGroup is getting scaled down and therefore has its RBAC deleted at the same time, but that failure doesn't stop prefs being updated within the local backend, doesn't affect connectivity to control, and the state Secret is about to get deleted anyway, so the only negative side effect is a harmless error log during shutdown. Control still learns that the node is no longer advertising the service and triggers the failover. Note that the first version of this used a PreStop lifecycle hook, but that only supports GET methods and we need the shutdown to trigger side effects (updating prefs) so it didn't seem appropriate to expose that functionality on a GET endpoint that's accessible on the k8s network. Updates tailscale/corp#24795 Change-Id: I0a9a4fe7a5395ca76135ceead05cbc3ee32b3d3c Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"tailscale.com/client/local"
|
|
"tailscale.com/client/tailscale/apitype"
|
|
)
|
|
|
|
// metrics is a simple metrics HTTP server, if enabled it forwards requests to
|
|
// the tailscaled's LocalAPI usermetrics endpoint at /localapi/v0/usermetrics.
|
|
type metrics struct {
|
|
debugEndpoint string
|
|
lc *local.Client
|
|
}
|
|
|
|
func proxy(w http.ResponseWriter, r *http.Request, url string, do func(*http.Request) (*http.Response, error)) {
|
|
req, err := http.NewRequestWithContext(r.Context(), r.Method, url, r.Body)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to construct request: %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
req.Header = r.Header.Clone()
|
|
|
|
resp, err := do(req)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to proxy request: %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
for key, val := range resp.Header {
|
|
for _, v := range val {
|
|
w.Header().Add(key, v)
|
|
}
|
|
}
|
|
w.WriteHeader(resp.StatusCode)
|
|
if _, err := io.Copy(w, resp.Body); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (m *metrics) handleMetrics(w http.ResponseWriter, r *http.Request) {
|
|
localAPIURL := "http://" + apitype.LocalAPIHost + "/localapi/v0/usermetrics"
|
|
proxy(w, r, localAPIURL, m.lc.DoLocalRequest)
|
|
}
|
|
|
|
func (m *metrics) handleDebug(w http.ResponseWriter, r *http.Request) {
|
|
if m.debugEndpoint == "" {
|
|
http.Error(w, "debug endpoint not configured", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
debugURL := "http://" + m.debugEndpoint + r.URL.Path
|
|
proxy(w, r, debugURL, http.DefaultClient.Do)
|
|
}
|
|
|
|
// registerMetricsHandlers registers a simple HTTP metrics handler at /metrics, forwarding
|
|
// requests to tailscaled's /localapi/v0/usermetrics API.
|
|
//
|
|
// In 1.78.x and 1.80.x, it also proxies debug paths to tailscaled's debug
|
|
// endpoint if configured to ease migration for a breaking change serving user
|
|
// metrics instead of debug metrics on the "metrics" port.
|
|
func registerMetricsHandlers(mux *http.ServeMux, lc *local.Client, debugAddrPort string) {
|
|
m := &metrics{
|
|
lc: lc,
|
|
debugEndpoint: debugAddrPort,
|
|
}
|
|
|
|
mux.HandleFunc("GET /metrics", m.handleMetrics)
|
|
mux.HandleFunc("/debug/", m.handleDebug) // TODO(tomhjp): Remove for 1.82.0 release.
|
|
}
|