Skip to content
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

feat(health): add check endpoint and loop control #2575

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
LOG_LEVEL=info \
# Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_SERVER_DISABLE_LOOP=off \
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
HEALTH_SUCCESS_WAIT_DURATION=5s \
HEALTH_VPN_DURATION_INITIAL=6s \
Expand Down
13 changes: 10 additions & 3 deletions internal/configuration/settings/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type Health struct {
SuccessWait time.Duration
// VPN has health settings specific to the VPN loop.
VPN HealthyWait
// Disable the healthcheck server loop. Useful for environments like Kubernetes which have their own healthcheck loops.
DisableLoop *bool
}

func (h Health) Validate() (err error) {
Expand All @@ -58,6 +60,7 @@ func (h *Health) copy() (copied Health) {
TargetAddress: h.TargetAddress,
SuccessWait: h.SuccessWait,
VPN: h.VPN.copy(),
DisableLoop: gosettings.CopyPointer(h.DisableLoop),
}
}

Expand All @@ -71,6 +74,7 @@ func (h *Health) OverrideWith(other Health) {
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithComparable(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN)
h.DisableLoop = gosettings.OverrideWithPointer(h.DisableLoop, other.DisableLoop)
}

func (h *Health) SetDefaults() {
Expand All @@ -83,6 +87,7 @@ func (h *Health) SetDefaults() {
const defaultSuccessWait = 5 * time.Second
h.SuccessWait = gosettings.DefaultComparable(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults()
h.DisableLoop = gosettings.DefaultPointer(h.DisableLoop, false)
}

func (h Health) String() string {
Expand All @@ -97,23 +102,25 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN"))
node.Appendf("Disable loop: %s", gosettings.BoolToYesNo(h.DisableLoop))
return node
}

func (h *Health) Read(r *reader.Reader) (err error) {
h.ServerAddress = r.String("HEALTH_SERVER_ADDRESS")
h.TargetAddress = r.String("HEALTH_TARGET_ADDRESS",
reader.RetroKeys("HEALTH_ADDRESS_TO_PING"))

h.SuccessWait, err = r.Duration("HEALTH_SUCCESS_WAIT_DURATION")
if err != nil {
return err
}

err = h.VPN.read(r)
if err != nil {
return fmt.Errorf("VPN health settings: %w", err)
}

h.DisableLoop, err = r.BoolPtr("HEALTH_SERVER_DISABLE_LOOP")
if err != nil {
return err
}
return nil
}
8 changes: 6 additions & 2 deletions internal/healthcheck/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ func (s *Server) Run(ctx context.Context, done chan<- struct{}) {
defer close(done)

loopDone := make(chan struct{})
go s.runHealthcheckLoop(ctx, loopDone)
if !*s.config.DisableLoop {
go s.runHealthcheckLoop(ctx, loopDone)
} else {
close(done)
}

server := http.Server{
Addr: s.config.ServerAddress,
Handler: s.handler,
Handler: s.mux,
ReadHeaderTimeout: s.config.ReadHeaderTimeout,
ReadTimeout: s.config.ReadTimeout,
}
Expand Down
15 changes: 14 additions & 1 deletion internal/healthcheck/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package healthcheck
import (
"context"
"net"
"net/http"

"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/models"
Expand All @@ -14,12 +15,13 @@ type Server struct {
dialer *net.Dialer
config settings.Health
vpn vpnHealth
mux *http.ServeMux
}

func NewServer(config settings.Health,
logger Logger, vpnLoop StatusApplier,
) *Server {
return &Server{
s := &Server{
logger: logger,
handler: newHandler(),
dialer: &net.Dialer{
Expand All @@ -32,7 +34,18 @@ func NewServer(config settings.Health,
loop: vpnLoop,
healthyWait: *config.VPN.Initial,
},
mux: http.NewServeMux(),
}
s.mux.Handle("/", s.handler)
s.mux.HandleFunc("/check/", func(w http.ResponseWriter, r *http.Request) {
err := s.healthCheck(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
return s
}

type StatusApplier interface {
Expand Down