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 ¶
- Variables
- func ErrorPolicyString(p ErrorPolicy) string
- func NewDefaultApp(ctx context.Context) (err error)
- func RegisterShutdownHandler(sh *ShutdownHandler)
- func RunAndWait(f MainLoopFunc)
- func SetupConfig(config interface{})
- func Shutdown(ctx context.Context) error
- type App
- type ErrorPolicy
- type MainLoopFunc
- type Probe
- type ProbeGroup
- type ShutdownFunc
- type ShutdownHandler
- type ShutdownPriority
Constants ¶
This section is empty.
Variables ¶
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" )
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 ¶
NewDefaultApp creates and sets the default app. The default app is controlled by public functions in app package
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.
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 (*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
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 ¶
IsOk returns the state of the probe (`true` or `false` for ok and not ok respectively).
type ProbeGroup ¶
type ProbeGroup struct {
// contains filtered or unexported fields
}
ProbeGroup aggregates and manages probes.
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.
type ShutdownFunc ¶
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.
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