Skip to content

Commit

Permalink
feat: Add httpserver metrics and health
Browse files Browse the repository at this point in the history
  • Loading branch information
David MICHENEAU committed Oct 4, 2024
1 parent 43ffcf8 commit 814e70e
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 76 deletions.
48 changes: 18 additions & 30 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import (
"log"
"os"
"os/signal"
"sync"
"syscall"

"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"

"github.com/orange-cloudavenue/kube-image-updater/internal/health"
"github.com/orange-cloudavenue/kube-image-updater/internal/httpserver"
client "github.com/orange-cloudavenue/kube-image-updater/internal/kubeclient"
"github.com/orange-cloudavenue/kube-image-updater/internal/metrics"
Expand Down Expand Up @@ -69,16 +67,16 @@ func init() {

// Start http server for webhook
func main() {
// !-- Context --! //
var err error

// -- Context -- //
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg := sync.WaitGroup{}

// -- OS signal handling -- //
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGKILL)

var err error

// homedir for kubeconfig
homedir, err := os.UserHomeDir()
if err != nil {
Expand All @@ -89,9 +87,14 @@ func main() {
panic(err)
}

// !-- Webhook server --! //
// -- Webhook server -- //
// generate cert for webhook
pair, caPEM := generateTLS()
tlsC := &tls.Config{
Certificates: []tls.Certificate{pair},
MinVersion: tls.VersionTLS12,
// InsecureSkipVerify: true, //nolint:gosec
}

// create or update the mutatingwebhookconfiguration
err = createOrUpdateMutatingWebhookConfiguration(caPEM, webhookServiceName, webhookNamespace, kubeClient)
Expand All @@ -100,42 +103,27 @@ func main() {
signalChan <- os.Interrupt
}

// Start the webhook server
wg.Add(1)
if err := StarWebhook(ctx, &wg, &tls.Config{
Certificates: []tls.Certificate{pair},
MinVersion: tls.VersionTLS12,
// InsecureSkipVerify: true, //nolint:gosec
}); err != nil {
// !-- Start the webhook server --! //
waitHTTP := httpserver.Init()
s := httpserver.New(httpserver.WithAddr(webhookPort), httpserver.WithTLSConfig(tlsC))
s.Router.Post(webhookPathMutate, serveHandler)
if err := s.Start(ctx); err != nil {
errorLogger.Fatalf("Failed to start webhook server: %v", err)
}

// !-- Prometheus metrics server --! //
// start the metrics server
if err := metrics.StartProm(ctx, &wg); err != nil {
if err = httpserver.StartMetrics(ctx); err != nil {
errorLogger.Fatalf("Failed to start metrics server: %v", err)
}

// !-- Health check server --! //
// start the health check server
if err := health.StartHealth(ctx, &wg); err != nil {
if err := httpserver.StartHealth(ctx); err != nil {
errorLogger.Fatalf("Failed to start health check server: %v", err)
}

// !-- OS signal handling --! //
// listening OS shutdown signal
<-signalChan
infoLogger.Printf("waiting for the server to shutdown gracefully...")
// cancel the context
cancel()
// wait all server for shutdown
wg.Wait()
// time.Sleep(2 * time.Second)
infoLogger.Printf("All servers are down: bye...")
}

func StarWebhook(ctx context.Context, wg *sync.WaitGroup, tlsC *tls.Config) (err error) {
s := httpserver.New(httpserver.WithAddr(webhookPort), httpserver.WithTLSConfig(tlsC))
s.Router.Post(webhookPathMutate, serveHandler)
return s.Start(ctx, wg)
waitHTTP()
}
15 changes: 3 additions & 12 deletions internal/health/health.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package health

import (
"context"
"flag"
"net"
"net/http"
"sync"
"time"

"github.com/orange-cloudavenue/kube-image-updater/internal/httpserver"
)

const (
Expand All @@ -26,24 +22,19 @@ func init() {
}

// healthHandler returns a http.Handler that returns a health check response
func healthHandler() http.Handler {
func Handler() http.Handler {
// TODO - Implement a new way to ask the health of the application (e.g. check image updater)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := net.DialTimeout("tcp", healthPort, timeoutR)
if err != nil {
return
}

// TODO - Implement an http.Handler content-type
w.Header().Set("Content-Type", "application/json")
_, err = w.Write([]byte(`{"status":"ok"}`))
if err != nil {
return
}
})
}

// ServeHealth starts the health check server
func StartHealth(ctx context.Context, wg *sync.WaitGroup) (err error) {
s := httpserver.New(httpserver.WithAddr(healthPort))
s.AddGetRoutes(healthPath, healthHandler())
return s.Start(ctx, wg)
}
112 changes: 97 additions & 15 deletions internal/httpserver/httpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,41 @@ import (

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"

"github.com/orange-cloudavenue/kube-image-updater/internal/health"
)

const (
timeout = 10 * time.Second
defaultPort = ":8080"
timeout = 10 * time.Second
timeoutR = 10 * time.Second
timeoutW = 10 * time.Second
defaultPort = ":8080"
defaultPortMetrics string = ":9080"
defaultPortHealth string = ":9081"
defaultPathMetrics string = "/metrics"
defaultPathHealth string = "/healthz"
)

var wg *sync.WaitGroup

type (
HTTPServer struct {
Router *chi.Mux
Config *http.Server
}
Option func(s *http.Server)
Option func(s *http.Server)
OptionMetrics func(port, path string)
)

// Func Init() initialize the waitgroup
// return a func to wait all server to shutdown gracefully
func Init() (waitStop func()) {
wg = &sync.WaitGroup{}
return WaitStop
}

// NewHTTPServer returns a new HTTP router
// func New(path, port string, tlsC *tls.Config) (s HTTPServer) {
func New(opts ...Option) *HTTPServer {
Expand All @@ -36,13 +55,19 @@ func New(opts ...Option) *HTTPServer {

// Default server configuration
s.Config = &http.Server{
Addr: defaultPort,
Handler: s.Router,
ReadTimeout: timeout,
Addr: defaultPort,
Handler: s.Router,
ReadTimeout: timeoutR,
WriteTimeout: timeoutW,
}
for _, opt := range opts {
opt(s.Config)
}
// check if waitgroup exist
// if not, create a new one
if wg == nil {
wg = &sync.WaitGroup{}
}
return s
}

Expand Down Expand Up @@ -72,23 +97,57 @@ func WithAddr(addr string) Option {
}
}

// WithTimeout sets the timeout for the HTTP server
// Add an option to set the timeout for the HTTP server
// The WithTimeout function takes a time.Duration as an argument and returns an Option
// The Option type is a function that takes a *http.Server as an argument
//
// ex: New(httpserver.WithTimeout(10*time.Second))
// ex: New(httpserver.WithTLSConfig(tlsC), httpserver.WithAddr(":8443"), httpserver.WithTimeout(10*time.Second))
func WithReadTimeout(timeout time.Duration) Option {
return func(s *http.Server) {
s.ReadTimeout = timeout
}
}

// WithWriteTimeout sets the write timeout for the HTTP server
// Add an option to set the write timeout for the HTTP server
// The WithWriteTimeout function takes a time.Duration as an argument and returns an Option
// The Option type is a function that takes a *http.Server as an argument
//
// ex: New(httpserver.WithWriteTimeout(10*time.Second))
// ex: New(httpserver.WithTLSConfig(tlsC), httpserver.WithAddr(":8443"), httpserver.WithWriteTimeout(10*time.Second))
func WithWriteTimeout(timeout time.Duration) Option {
return func(s *http.Server) {
s.WriteTimeout = timeout
}
}

// WithHandler sets the handler for the HTTP server
// Add an option to set the handler for the HTTP server
// The WithHandler function takes a http.Handler as an argument and returns an Option
// The Option type is a function that takes a *http.Server as an argument
//
// ex: New(httpserver.WithHandler(handler))
// ex: New(httpserver.WithTLSConfig(tlsC), httpserver.WithAddr(":8443"), httpserver.WithHandler(handler))
func WithHandler(handler http.Handler) Option {
return func(s *http.Server) {
s.Handler = handler
}
}

// Add Get routes to the HTTP server
func (s HTTPServer) AddGetRoutes(path string, handler http.Handler) {
func (s *HTTPServer) AddGetRoutes(path string, handler http.Handler) {
s.Router.Mount(path, handler)
}

// Add Post routes to the HTTP server
func (s HTTPServer) AddPostRoutes(path string, handler http.Handler) {
func (s *HTTPServer) AddPostRoutes(path string, handler http.Handler) {
s.Router.Mount(path, handler)
}

// ServeHTTP implements the http.Handler interface
func (s HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Router.ServeHTTP(w, r)
}

// ListenAndServe starts the HTTP server
func (s HTTPServer) Start(ctx context.Context, wg *sync.WaitGroup) (err error) {
func (s *HTTPServer) Start(ctx context.Context) (err error) {
wg.Add(1)
defer wg.Done()

Expand All @@ -97,7 +156,6 @@ func (s HTTPServer) Start(ctx context.Context, wg *sync.WaitGroup) (err error) {
// Start the HTTP server
go func() {
log.Infof("Starting server on %s", s.Config.Addr)
// log.Printf("Starting server on %s", s.Config.Addr)
if err = s.Config.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return
}
Expand All @@ -117,6 +175,7 @@ func (s HTTPServer) Start(ctx context.Context, wg *sync.WaitGroup) (err error) {
go func() {
for {
<-ctx.Done()
wg.Add(1)
defer wg.Done()
ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Second)
log.Infof("Shutting down server on %s", s.Config.Addr)
Expand All @@ -129,3 +188,26 @@ func (s HTTPServer) Start(ctx context.Context, wg *sync.WaitGroup) (err error) {
}()
return nil
}

// StartMetrics starts the HTTP server for Metrics
// With default port (:9080) and path /metrics
func StartMetrics(ctx context.Context, opts ...Option) (err error) {
s := New(WithAddr(defaultPortMetrics))
s.AddGetRoutes(defaultPathMetrics, promhttp.Handler())
return s.Start(ctx)
}

// StartHealth starts the HTTP server for Health
// With default port (:9081) and path /healthz
func StartHealth(ctx context.Context, opts ...Option) (err error) {
s := New(WithAddr(defaultPortHealth))
s.AddGetRoutes(defaultPathHealth, health.Handler())
return s.Start(ctx)
}

// func use to wait ALL HTTP server to shutdown gracefully
func WaitStop() {
log.Info("Waiting for all server to shutdown gracefully...")
wg.Wait()
log.Info("All Server on has been shutdown: bye...")
}
26 changes: 7 additions & 19 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
package metrics

import (
"context"
"flag"
"sync"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"

"github.com/orange-cloudavenue/kube-image-updater/internal/httpserver"
)

var (
metricsPath string = "/metrics"
metricsPort string = ":9080"
)

// NewCounter creates a new Prometheus counter
Expand Down Expand Up @@ -70,15 +58,15 @@ func NewSummary(name, help string) prometheus.Summary {
}

func init() {
flag.StringVar(&metricsPort, "metrics-port", metricsPort, "Metrics server port. ex: :9080")
flag.StringVar(&metricsPath, "metrics-path", metricsPath, "Metrics server path. ex: /metrics")
// flag.StringVar(&metricsPort, "metrics-port", metricsPort, "Metrics server port. ex: :9080")
// flag.StringVar(&metricsPath, "metrics-path", metricsPath, "Metrics server path. ex: /metrics")
}

// ServeProm starts a Prometheus metrics server
// TODO - Add context to cancel the server
// in order to stop the server gracefully
func StartProm(ctx context.Context, wg *sync.WaitGroup) (err error) {
s := httpserver.New(httpserver.WithAddr(metricsPort))
s.AddGetRoutes(metricsPath, promhttp.Handler())
return s.Start(ctx, wg)
}
// func Start(ctx context.Context) (err error) {
// s := httpserver.New(httpserver.WithAddr(metricsPort))
// s.AddGetRoutes(metricsPath, promhttp.Handler())
// return s.Start(ctx)
// }

0 comments on commit 814e70e

Please sign in to comment.