153 lines
3.8 KiB
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()
|
|
}
|
|
}
|