http-add-on/scaler/main.go

153 lines
3.8 KiB
Go

// The HTTP Scaler is the standard implementation for a KEDA external scaler
// which can be found at https://keda.sh/docs/2.0/concepts/external-scalers/
// This scaler has the implementation of an HTTP request counter and informs
// KEDA of the current request number for the queue in order to scale the app
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"time"
"github.com/go-logr/logr"
"github.com/kedacore/http-add-on/pkg/k8s"
pkglog "github.com/kedacore/http-add-on/pkg/log"
externalscaler "github.com/kedacore/http-add-on/proto"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
lggr, err := pkglog.NewZapr()
if err != nil {
log.Fatalf("error creating new logger (%v)", err)
}
ctx := context.Background()
cfg := mustParseConfig()
grpcPort := cfg.GRPCPort
healthPort := cfg.HealthPort
namespace := cfg.TargetNamespace
svcName := cfg.TargetService
targetPortStr := fmt.Sprintf("%d", cfg.TargetPort)
targetPendingRequests := cfg.TargetPendingRequests
k8sCl, _, err := k8s.NewClientset()
if err != nil {
lggr.Error(err, "getting a Kubernetes client")
os.Exit(1)
}
pinger := newQueuePinger(
context.Background(),
lggr,
k8s.EndpointsFuncForK8sClientset(k8sCl),
namespace,
svcName,
targetPortStr,
time.NewTicker(500*time.Millisecond),
)
grp, ctx := errgroup.WithContext(ctx)
grp.Go(
startGrpcServer(
ctx,
lggr,
grpcPort,
pinger,
int64(targetPendingRequests),
),
)
grp.Go(startHealthcheckServer(ctx, lggr, healthPort, pinger))
lggr.Error(grp.Wait(), "one or more of the servers failed")
}
func startGrpcServer(
ctx context.Context,
lggr logr.Logger,
port int,
pinger *queuePinger,
targetPendingRequests int64,
) func() error {
return func() error {
addr := fmt.Sprintf("0.0.0.0:%d", port)
lggr.Info("starting grpc server", "address", addr)
lis, err := net.Listen("tcp", addr)
if err != nil {
return err
}
grpcServer := grpc.NewServer()
externalscaler.RegisterExternalScalerServer(
grpcServer,
newImpl(lggr, pinger, targetPendingRequests),
)
reflection.Register(grpcServer)
go func() {
<-ctx.Done()
lis.Close()
}()
return grpcServer.Serve(lis)
}
}
func startHealthcheckServer(
ctx context.Context,
lggr logr.Logger,
port int,
pinger *queuePinger,
) func() error {
lggr = lggr.WithName("startHealthcheckServer")
return func() error {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
mux.HandleFunc("/livez", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
mux.HandleFunc("/queue", func(w http.ResponseWriter, r *http.Request) {
lggr = lggr.WithName("route.counts")
cts := pinger.counts()
lggr.Info("counts endpoint", "counts", cts)
if err := json.NewEncoder(w).Encode(&cts); err != nil {
lggr.Error(err, "writing counts information to client")
w.WriteHeader(500)
}
})
mux.HandleFunc("/queue_ping", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
lggr := lggr.WithName("route.counts_ping")
if err := pinger.requestCounts(ctx); err != nil {
lggr.Error(err, "requesting counts failed")
w.WriteHeader(500)
w.Write([]byte("error requesting counts from interceptors"))
return
}
cts := pinger.counts()
lggr.Info("counts ping endpoint", "counts", cts)
if err := json.NewEncoder(w).Encode(&cts); err != nil {
lggr.Error(err, "writing counts data to caller")
w.WriteHeader(500)
w.Write([]byte("error writing counts data to caller"))
}
})
srv := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
}
lggr.Info("starting health check server", "port", port)
go func() {
<-ctx.Done()
srv.Shutdown(ctx)
}()
return srv.ListenAndServe()
}
}