app

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2024 License: BSD-3-Clause Imports: 25 Imported by: 3

Documentation

Overview

Package app provides an application framework that manages the live cycle of a running application.

An app could be an HTTP server (or any kind of server), a worker or a simple program. Independently of the kind of the app, it always exposes an admin port at 9000 by default which serves metrics, debug information and kubernetes probes. It also is capable of graceful shutdown on receiving signals of terminating by itself.

The recommended way to use the `app` package is to rely on the 'default app'. The 'default app' is a global app that can be accessed by public functions on the app package (the app package can be seen as the application)

There is a running example on `app/examples/servefiles`

Basics

The first thing you should do is setup a configuration and logger. The app uses the foundation's kit log package and expects a zerolog's logger in the context.

app.SetupConfig(&config)
ctx := log.SetupLoggerWithContext(context.Background(), config.Log, version)
app.NewDefaultApp(ctx)

At this point, the app will already be exposing the admin port and the readiness probe will be returning error, indicating that the application is not yet ready to receive requests.

Then you should start initializing all the program dependencies. Because the application is not yet ready, kubernetes will refrain from sending requests (that would fail at this point). Also we already have some metrics and the debug handlers.

During this phase, you will probably want to register your shutdown handlers.

app.RegisterShutdownHandler(
	&app.ShutdownHandler{
		Name:     "http_server",
		Priority: app.ShutdownPriority(100),
		Handler:  httpServer.Shutdown,
		Policy:   app.ErrorPolicyAbort,
	},
)

They are executed in order by priority. The Highest priority first (in case of the same priority, don't assume any order).

Finally you can run the application by calling RunAndWait:

app.RunAndWait(func() error {
	return httpServer.ListenAndServe()
})

At this point the application will run until the given function returns or it receives an termination signal.

Updating From Previous Version

On the previous version,the NewDefaultApp received the main loop:

func NewDefaultApp(ctx context.Context, mainLoop MainLoopFunc) (err error)

This was a problem because the main loop normally depends on various resources that must be created before the main loop can be called. But the creation of this resourced involves registering shutdown handlers, that requires an already created app.

This cycle forced the application to rely on lazy initialization of the resources. Lazy initialization is not a bad thing but in this particular case this means that when we call RunAndWait and the readiness probe is set return success, the application is still initializing and could start receiving requests before it was really ready.

To break this cycle the main loop was moved from the NewDefaultApp and was placed on the RunAndWait function. So to update to this version you could only change these two functions calls. But, to really take advantage of this new way to start an app, you should refactor the code to remove the laziness part before the RunAndWait is called.

Using Probes

A Probe is a boolean that indicates if something is OK or not. There are two groups of probes in an app: The Healthiness an Readiness groups. Kubernetes checks on there two probes to decide what to do to the pod, like, from stop sending requests to just kill the pod, sending a signal the app will capture and start a graceful shutdown.

If a single probe of a group is not ok, than the whole group is not ok. In this event, the HTTP handler returns the name of all the probes that are not ok for the given group.

mux.HandleFunc("/healthy", func(w http.ResponseWriter, _ *http.Request) {
	isHealthy, cause := app.Healthy.CheckProbes()
	if isHealthy {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	} else {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(cause))
	}
})
mux.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) {
	isReady, cause := app.Ready.CheckProbes()
	if isReady {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	} else {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(cause))
	}
})

If the application is unhealthy kubernetes will send a signal that will trigger the graceful shutdown. All registered shutdown handlers will be executed ordered by priority (highest first) and the pod will be restarted. Only set an application as unhealthy if it reached an unworkable state and should be restarted. We have an example of this on `gokitmiddlewares/stalemiddleware/`. This is a middleware that was developed to be used in workers. It checks if the endpoint is being called (messages are being fetched and processed) and if not, it assumes there could be a problem with the queue and sets the application to unready, causing the application to restart. This mitigated a problem we had with kafka when a change of brokers made the worker stop receiving messages forever.

If the application is unready kubernetes will stop sending requests, but if the application becomes ready again, it will start receiving requests. This is used during initialization to signalize to kubernetes when the application is ready and can receive requests. If we can identify that the the application is degraded we can use this probe to temporary remove the application from the kubernetes service until it recovers.

A probe only exists as part of a group so the group provides a proper constructor for a probe. Probe's name must also be unique for the group but can be reused on different groups.

readinessProbe, err := app.Ready.NewProbe("fkit/app", false)
healthnessProbe, err := app.Healthy.NewProbe("fkit/app", true)

The probe is automatically added to the group and any change is automatically reflected on the group it belongs to and the HTTP probe endpoints.

The state of a probe can be altered at any time using SetOk and SetNotOk:

readinessProbe.SetOk()
readinessProbe.SetNotOk()

Index

Constants

This section is empty.

Variables

View Source
var (
	// DefaultGracePeriod is the default value for the grace period.
	// During normal shutdown procedures, the shutdown function will wait
	// this amount of time before actually starting calling the shutdown handlers.
	DefaultGracePeriod = 3 * time.Second
	// DefaultShutdownTimeout is the default value for the timeout during shutdown.
	DefaultShutdownTimeout = 5 * time.Second
	// DefaultAdminPort is the default port the app will bind the admin HTTP interface.
	DefaultAdminPort = "9000"
)
View Source
var ConfigFilename = "config.json"

ConfigFilename is the filename of the config file automatically loaded by SetupConfig

Functions

func ErrorPolicyString

func ErrorPolicyString(p ErrorPolicy) string

ErrorPolicyString returns a string representation of a ErrorPolicy. This was intended for logging purposes.

func NewDefaultApp

func NewDefaultApp(ctx context.Context) (err error)

NewDefaultApp creates and sets the default app. The default app is controlled by public functions in app package

func Recover added in v0.3.1

func Recover()

Recover recovers from the panic and if panic persists, logs it with trace error. Should be called at the first line in the main function.

func RegisterShutdownHandler

func RegisterShutdownHandler(sh *ShutdownHandler)

RegisterShutdownHandler calls the RegisterShutdownHandler from the default app

func RunAndWait

func RunAndWait(f MainLoopFunc)

RunAndWait calls the RunAndWait of the default app

func SetupConfig

func SetupConfig(config interface{})

SetupConfig loads the configuration in the given struct. In case of error, prints help and exit application.

func Shutdown

func Shutdown(ctx context.Context) error

Shutdown calls the Shutdown of the default app

Types

type App

type App struct {
	Ready   ProbeGroup
	Healthy ProbeGroup

	GracePeriod     time.Duration
	ShutdownTimeout time.Duration
	// contains filtered or unexported fields
}

App represents an application with a main loop and a shutdown routine

func MustNew

func MustNew(ctx context.Context, adminPort string) *App

MustNew returns a new App, but panics if there is an error.

func New

func New(ctx context.Context, adminPort string) (*App, error)

New returns a new App. If ctx contains a zerolog logger it is used for logging. adminPort must be a valid port number or it will fail silently.

func (*App) RegisterShutdownHandler

func (a *App) RegisterShutdownHandler(sh *ShutdownHandler)

RegisterShutdownHandler adds a handler in the end of the list. During shutdown all handlers are executed in the order they were added

func (*App) RunAndWait

func (a *App) RunAndWait(mainLoop MainLoopFunc)

RunAndWait executes the main loop on a go-routine and listens to SIGINT and SIGKILL to start the shutdown

func (*App) Shutdown

func (a *App) Shutdown(ctx context.Context) (err error)

Shutdown calls all shutdown methods, in order they were added.

type ErrorPolicy

type ErrorPolicy int

ErrorPolicy specifies what should be done when a handler fails

const (
	// ErrorPolicyWarn prints the error as a warning and continues to the next handler. This is the default.
	ErrorPolicyWarn ErrorPolicy = iota
	// ErrorPolicyAbort stops the shutdown process and returns an error
	ErrorPolicyAbort
	// ErrorPolicyFatal logs the error as Fatal, it means the application will close immediately
	ErrorPolicyFatal
	// ErrorPolicyPanic panics if there is an error
	ErrorPolicyPanic
)

type MainLoopFunc

type MainLoopFunc func() error

MainLoopFunc is the functions runned by app. If it finishes, it will trigger a shutdown

type Probe

type Probe struct {
	// contains filtered or unexported fields
}

Probe stores the state of a probe (`true` or `false` for ok and not ok respectively).

func (*Probe) IsOk

func (p *Probe) IsOk() bool

IsOk returns the state of the probe (`true` or `false` for ok and not ok respectively).

func (*Probe) Set

func (p *Probe) Set(ok bool)

Set changes the state of a probe. Use `true` for ok and `false` for not ok.

func (*Probe) SetNotOk

func (p *Probe) SetNotOk()

SetNotOk sets the probe as not ok. Same as `Set(false)`.

func (*Probe) SetOk

func (p *Probe) SetOk()

SetOk sets the probe as ok. Same as `Set(true)`.

type ProbeGroup

type ProbeGroup struct {
	// contains filtered or unexported fields
}

ProbeGroup aggregates and manages probes.

func HealthinessProbeGroup

func HealthinessProbeGroup() *ProbeGroup

HealthinessProbeGroup TODO

func NewProbeGroup

func NewProbeGroup() ProbeGroup

NewProbeGroup returns a new ProbeGroup.

func ReadinessProbeGoup

func ReadinessProbeGoup() *ProbeGroup

ReadinessProbeGoup TODO

func (*ProbeGroup) CheckProbes

func (m *ProbeGroup) CheckProbes() (bool, string)

CheckProbes range through the probes and returns the state of the group. If any probe is not ok (false) the group state is not ok (false). If the group is not ok, it's also returned the cause in the second return parameter. If more than one probe is not ok, the causes are concatenated by a comma.

func (*ProbeGroup) MustNewProbe

func (m *ProbeGroup) MustNewProbe(name string, ok bool) Probe

MustNewProbe returns a new Probe with the given name and panics in case of error.

func (*ProbeGroup) NewProbe

func (m *ProbeGroup) NewProbe(name string, ok bool) (Probe, error)

NewProbe returns a new Probe with the given name.

type ShutdownFunc

type ShutdownFunc func(context.Context) error

ShutdownFunc is a shutdown function that will be executed when the app is shutting down.

type ShutdownHandler

type ShutdownHandler struct {
	Name    string
	Timeout time.Duration
	Handler ShutdownFunc
	Policy  ErrorPolicy

	Priority ShutdownPriority
	// contains filtered or unexported fields
}

ShutdownHandler is a shutdown structure that allows configuring and storing shutdown information of an orchestrated shutdown flow.

func (*ShutdownHandler) Execute

func (sh *ShutdownHandler) Execute(ctx context.Context) error

Execute runs the shutdown functions and handles timeout and error policy

type ShutdownPriority

type ShutdownPriority uint8

ShutdownPriority is used to guide the execution of the shutdown handlers during a graceful shutdown. The shutdown is performed from the higher to the lowest priority

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL