Repository navigation
Replies: 3 comments
Revision: a probes server owns its readinessThe first post put a health Service next to every probes server. A consumer's pod returned The first post's shape had one wiring for every pod, and two costs:
The lifecycles don't get in the way. A health Service stops on its Open questions 1 to 4 are unchanged, and still open. Under question 2, attaching the probes now also means building and returning a health Service, and a probes port doesn't. Proposal: RabbitMQ needs nothing: Each section below replaces the part of the same name in the post. Background, the HTTP and gRPC answer tables, Startup probes, Checks, Telemetry, A consumer's readiness, What waves 5 and 6 build on this, Open questions, Out of scope and References are unchanged. SummaryThe first, second and fourth bullets become:
The consumer's pod becomes: func Init(ctx context.Context, s *humus.Setup) ([]humus.Service, error) {
settings, probeSettings, err := readSettings(ctx, s) // kafka.Settings and httphealth.Settings
if err != nil {
return nil, err
}
consumer, err := kafka.NewService(s, settings, queue.ProcessorFunc[kafka.Message](h.Handle))
if err != nil {
return nil, err
}
probes, err := httphealth.NewService(s, probeSettings, health.Readiness(health.MonitorFunc(consumer.Joined)))
if err != nil {
return nil, err
}
return []humus.Service{consumer, probes}, nil
}Proposed APIIn // Service is a humus.Service that serves NewHandler's handler, and nothing
// else, over plain HTTP/1.1 on a port of its own. It owns the App's
// readiness as a health.Service does, so the end user's Init returns no
// health Service next to it. It is safe for concurrent use.
type Service struct{ /* unexported */ }
// NewService checks settings and opts, reporting every problem in one
// error, and listens on settings.Addr through s.Host. It registers the
// listener's close with s.OnStop, so a listener whose Service never serves,
// because Init failed after NewService, is still closed.
//
// The Service answers from the probes of a health.Service built from opts:
//
// - readiness is false until Serve starts, false for good once Serve's
// context is done or Shutdown is called, and otherwise checks the
// monitors health.Readiness adds, in the order they were added;
// - liveness checks the monitors health.Liveness adds, in the order they
// were added, and is healthy when there are none.
//
// NewService rejects a nil Option.
func NewService(s *humus.Setup, settings Settings, opts ...health.Option) (*Service, error)
// Serve marks the Service ready, and serves until Shutdown. It keeps
// serving after ctx is done, so the probes are answered, not ready, through
// the App's drain.
func (svc *Service) Serve(ctx context.Context) error
// Shutdown marks the Service not ready for good, stops accepting
// connections and waits for the probes in flight. If ctx is done first, it
// closes every connection and returns an error that wraps ctx's error.
// Calling it again returns nil.
func (svc *Service) Shutdown(ctx context.Context) errorIn // Service is a humus.Service that serves NewServer's server, and nothing
// else, over plaintext gRPC on a port of its own. It owns the App's
// readiness as a health.Service does, so the end user's Init returns no
// health Service next to it.
type Service struct{ /* unexported */ }
// NewService checks settings and opts, reporting every problem in one
// error, listens on settings.Addr through s.Host, and registers NewServer's
// server, answering from the probes of a health.Service built from opts,
// as httphealth.NewService does. It is served and shut down as a
// grpcserver.Service is, without TLS and without instrumentation.
// NewService rejects a nil Option.
func NewService(s *humus.Setup, settings Settings, opts ...health.Option) (*Service, error)
Wiring the probesThe paragraph before the table becomes: The end user's
The API with the handler attached is unchanged. The gRPC service with its health server on a plaintext port becomes: svc, err := grpcserver.NewService(s, settings)
if err != nil {
return nil, err
}
cartpb.RegisterCartServiceServer(svc, carts)
probes, err := grpchealth.NewService(s, probeSettings)
if err != nil {
return nil, err
}
return []humus.Service{svc, probes}, nil"One health Service's The probes serversA bullet is added before "They serve through the drain":
JobsThe last two sentences become: A long-running job that wants a liveness probe returns Changes to landed packages and pagesThe bullets on package docs and the docs site become:
Design decisionsThe bullet "Probes are explicit Services" becomes:
The bullet "
TestingThis revision's change was not prototyped. The first post's prototypes ran a probes server next to a health Service, and the probes server now runs that health Service itself. The health Service's lifecycle is landed and tested.
svc, err := httphealth.NewService(s, httphealth.Settings{Addr: ":8081"})In the drain test, The bullet on the settings convention becomes, and a bullet is added after it:
Migration from todayThe
|
Revision 2: the open questions answeredThe open questions:
Proposal: RabbitMQ's Each section below replaces the part of the same name in the post, as revision 1 left it. Background, the HTTP and gRPC answer tables, Checks, Jobs, What waves 5 and 6 build on this, Migration from today and References are unchanged. Summary
A consumer's pod, for example: func Init(ctx context.Context, s *humus.Setup) ([]humus.Service, error) {
settings, probeSettings, err := readSettings(ctx, s) // kafka.Settings and httphealth.Settings
if err != nil {
return nil, err
}
consumer, err := kafka.NewService(s, settings, queue.ProcessorFunc[kafka.Message](h.Handle))
if err != nil {
return nil, err
}
probes, err := httphealth.NewService(s, probeSettings, health.Readiness(consumer.Joined()))
if err != nil {
return nil, err
}
return []humus.Service{consumer, probes}, nil
}Proposed APIIn // Settings configures a Service. Neither of its fields has a default, so
// the end user always sources both.
type Settings struct {
// Addr is unchanged.
Addr string
// TLS is the config the Service serves HTTPS with. It is required: the
// Service serves nothing in plain text. The Service serves a clone of
// it, offering only HTTP/1.1, so the end user's config is never changed.
// The kubelet doesn't verify the certificate of a probe with
// scheme: HTTPS, and presents none of its own.
//
// Default: none. Valid: a config with a certificate, that doesn't
// require a client certificate.
// Check: CheckTLS.
TLS *tls.Config
}
// CheckTLS rejects a nil config, one with no certificate, where none of
// Certificates, GetCertificate and GetConfigForClient is set, and one whose
// ClientAuth requires a client certificate, which the kubelet never
// presents, without naming the field.
func CheckTLS(c *tls.Config) error
In // Joined returns a monitor that is healthy once the Service has joined its
// consumer group: unhealthy until franz-go first hands the member an
// assignment, empty or not, and healthy from then until Serve returns. A
// broker outage after that doesn't make it unhealthy. It never returns an
// error. A consumer is made ready once it has joined with
//
// health.Readiness(consumer.Joined())
//
// It is for readiness only: restarting the pod doesn't bring the brokers
// back, and an error that a restart would fix already ends Serve.
func (s *Service) Joined() health.MonitorWiring the probesThe paragraph before the table, and the table, become: The end user's
The examples are as revision 1 has them. The probes servers
Startup probesThe startup probe in the manifest sets startupProbe:
httpGet:
path: /health/liveness
port: probes
scheme: HTTPS
failureThreshold: 30 # 30 × 2s: Init has a minute
periodSeconds: 2TelemetryThe last sentence of "Attached probes are traced like any request" becomes: A team that doesn't want the spans drops them in its sampler. A consumer's readinessThe last paragraph's last sentence, "Liveness never checks the brokers: restarting the pod doesn't bring them back", is replaced by a paragraph of its own:
Changes to landed packages and pagesThe bullets on
Design decisionsThe bullet "A probes server serves the probes and nothing else" becomes:
The bullet "A consumer is ready once joined" becomes:
These are added:
Testing
cert := humustest.NewCA(t).Issue(t, "localhost")
svc, err := httphealth.NewService(s, httphealth.Settings{
Addr: ":8081",
TLS: &tls.Config{Certificates: []tls.Certificate{cert.TLS()}},
})In the drain test, the probes are requested at The bullets on
Open questionsAll four are answered above. None are left. Out of scopeThese are added:
|
Uh oh!
There was an error while loading. Please reload this page.
Note
Part of the humus re-evaluation; see Proposal: humus on the bedrock process model, wave 4. It builds on Proposal: health monitors and probes, Proposal: humus.Main, and the four wave-3 proposals: HTTP server foundation, gRPC services, jobs and queue processing and the Kafka runtime. It depends on no other wave-4 proposal. It answers the questions health monitors and probes handed on: the probe paths, the response bodies, the gRPC health v1 answers, startup probes and caching. It also answers the Kafka proposal's open question 5, whether a consumer whose brokers are unreachable is ready.
Summary
healthgives an App its probes,health.Probes, and a health Service that owns readiness, and serves no protocol. This proposal serves those probes to an orchestrator, in two new packages and one new method:httphealth, in the core module.NewHandleranswersGET /health/livenessandGET /health/readinessfrom an App'sProbes, with 200 or 503 and no detail in the body.NewServiceserves that handler, and nothing else, over plain HTTP on a port of its own (open question 1), for a pod with no HTTP server of its own, such as a job's or a consumer's. The handler can also be attached to an HTTPS server the App already runs.grpc/grpchealth, in thegrpcmodule.NewServerimplements gRPC health v1 from the sameProbes. The service names""andreadinessanswer readiness, andlivenessanswers liveness, as Kubernetes recommends.NewServiceserves it in plain text on a port of its own, with nothing else registered, as gRPC services planned. It can also be registered on the end user'sgrpcserver.Service.(*kafka.Service).Joined, a method whose signature ishealth.MonitorFunc's. A consumer is ready once it has joined its group, so a rollout whose new pods can't reach the brokers stops, rather than replacing pods that consume with pods that don't.Initreturns a health Service and whatever exposes it, like any other Service. No runtime package'sMainexposes probes by default: humus imposes no port, so it has nowhere to serve them.Inithas returned, so a startup probe on the liveness path already waits forInit. Each probe request checks its monitors with the request's context, and the probe's own timeout bounds the check.A consumer's pod, for example:
Background
healthprovides (health/service.go).Probesholds a liveness and a readinessMonitor.health.Service.Probesreturns them, with the Service's own state first in readiness: not ready until itsServestarts, and not ready for good once itsServecontext is done, which is when shutdown starts, before the drain.healthis a leaf that imports nothing but the standard library, and it opens nothing and logs nothing. Whatever exposes the probes logs a monitor's error (health's revision 2).Probesif they get a probe of their own), and caching. Its first revision's decisions about exposure moved here too: probes on a port of their own, a handler that adds no timeout, failures logged and not returned, and a gRPC health v1 table as a starting point.rest.NewApiservedGET /health/livenessandGET /health/readinesson the API's own mux, answering 200 unless the end user replaced them withrest.Livenessandrest.Readiness(rest/api.go#L46-L67at v0.15.0). story(issue-484): rest migrate to bedrock v0210 #485 removed them, and the README went on promising them.internal/grpchealth/server.go). Nothing answered the empty service name, which Kubernetes' gRPC probe sends by default (L56). A monitor's error answeredSERVICE_UNKNOWN, which the protocol reserves for aWatchon an unknown service (L70).Watchpolled every 10s (L36), and logging went through the global logger (L34).httpserver.NewService, given a probe handler. TLS is required, so a probes server built onhttpserverneeds a certificate. Whether a job's or a consumer's pod serves probes that way, whether anyMainexposes probes by default, and whether probe requests get spans are decided here.GRPCContainerProbeTLSgate is on, and never present a client certificate. So gRPC health v1 goes on a second, plaintext gRPC server, on a port of its own, with onlygrpc.health.v1.Healthregistered, built on thegrpcmodule'sinternal/server.*grpcserver.Serviceis agrpc.ServiceRegistrar, so health v1 can be registered on it too.httpGetprobe succeeds on a status from 200 to 399, decided by the status alone. The kubelet reads at most 10 KiB of the body, and recommends a minimal one. It speaks HTTP/1.1, and overscheme: HTTPSit doesn't verify the certificate;grpcprobe connects in plain text by default. Itsservicefield picks the service name, and Kubernetes suggestslivenessandreadinessto answer the two probes on one port;failureThreshold × periodSecondsthat covers the slowest start.Proposed API
httphealthis in the core module, next tohttpserver, and imports nothing new.grpchealthis in thegrpcmodule, next togrpcserver: it needs grpc-go'sgrpc_health_v1, which is in the grpc-go module thegrpcmodule already requires, and the module'sinternal/server.healthitself doesn't change and stays a leaf.Wiring the probes
The end user's
Initbuilds a health Service, hands itsProbesto whatever exposes them, and returns them all. Which exposure fits depends on what else the pod serves:mainhumus.Mainhttphealth.NewServiceon a port of its ownhttpGeton that porthttpserver.Mainhttphealth.NewHandlerattached to the API's mux, orhttphealth.NewService(open question 2)httpGet,scheme: HTTPSon the API's port, orscheme: HTTPon the probes portgrpcserver.Maingrpchealth.NewServiceon a plaintext port of its own, orgrpchealth.NewServerregistered on the end user's Service where probes can use TLS (open question 2)grpcwithservice: livenessorreadinesshumus.MainA probes server adds no drain: nothing routes traffic to it. So a consumer's or a job's pod with a probes server stays on
humus.Main, as revision 6 of the charter has it.An API with the handler attached:
A gRPC service with its health server on a plaintext port:
One health Service's
Probescan be handed to both packages, to answer HTTP and gRPC probes from one state.HTTP: paths, bodies and status codes
GETorHEAD /health/livenessOKService UnavailableGETorHEAD /health/readinessOKService Unavailable/livezand/readyzare what Kubernetes' own components serve, and other frameworks use their own.Content-Type: text/plain; charset=utf-8andCache-Control: no-store. The kubelet decides on the status code alone, and a person withcurlsees which it was. A monitor's error is never in the body: it can name a host or a credential's user, and a probe port is reachable by anything that can reach the pod.Monitorstays one method with no name, as health decided. The failing monitor's error is in the log record.The handler routes on the full path, with an
http.ServeMuxof its own, so it works mounted at"/health/"of the end user's mux, and serves 404 for anything else under it.The probes servers
httphealth.NewServiceandgrpchealth.NewServiceserve plain text (open question 1). Each answers its probes and nothing else, so nothing of the end user's is reachable on that port, with or without TLS on their own ports.Shutdown, like an HTTP or gRPC Service. During an API's drain, a readiness probe is answered 503, not refused, and a liveness probe still 200.httpserver. The listener,Serve,Shutdownwith itsClosewhen the grace period runs out, andnet/http's error log written to telemetry move fromhttpserverinto an internal package of the core module, which knows nothing of TLS.httpserver.NewServiceis that code with TLS required, HTTP/2 and its instrumentation, andhttphealth.NewServiceis that code over plain HTTP/1.1, with the probe handler alone. This is the shape thegrpcmodule already has, andhttpserverserves exactly as before.grpcmodule'sinternal/server. That package already listens through the Host, recovers panics, ends streams atShutdownwithUnavailable, and callsGracefulStopand thenStop.grpchealth.NewServicepasses it no credentials and no stats handler.httpserverdefaults, and sets no write timeout, since the probe's own timeout bounds a check. Neither server has a setting besidesAddr. A field is added when an end user needs one, which breaks no one.H2CContainerProbegate is on and the probe asks for h2c, and humus serves no h2c.grpcserver,httpserverand theirMains are unchanged. NeitherMainruns a health Service or serves probes, as their docs already say.gRPC health v1: the answers
CheckWatch""readinesslivenessNotFoundSERVICE_UNKNOWN, and the stream stays openSERVINGSERVINGNOT_SERVING; an error is loggedNOT_SERVING; an error is logged onceListanswers the three names with their current status.""is readiness. It is the overall status, which agrpcprobe withoutservice,grpc-health-probeand grpc-go's client-side health checking all ask for.livenessandreadinessare Kubernetes' names. Its docs suggest them for answering both probes on one port.cart.v1.CartService, answersNotFound, as any unknown name does.NOT_SERVING, notSERVICE_UNKNOWN, which the protocol keeps for aWatchon an unknown name.health.Server. grpc-go's holds statuses that the application sets. humus's checks the monitors when it is asked, so it needs nothing to keep a status in step.Watchpolls, once a second, once per server and name. Monitors are checked when asked and send no changes, so aWatchchecks the monitor every second and sends the status when it changes, starting with the current one. EveryWatchon one server for one name shares one poller, which runs while the name has watchers. So a hundred clients watching""cost one check a second, not a hundred. Each poll checks with a context that is done after a second, and a check that runs out of time isNOT_SERVING. A failing monitor is logged when the status changes, not once a second.Watchis what grpc-go's client-side health checking uses, against a health server registered on the end user's Service. With a one-second poll, a client learns that a pod is draining within a second of shutdown starting, well withingrpcserver.Main's 5s drain, and moves its RPCs to other pods before the GOAWAY. AtShutdown,internal/servercancels every stream, and aWatchends withUnavailable, as every stream does. The interval is fixed. A setting is added if an end user needs another.Startup probes
There is no startup probe of its own, and
Probesgets no field. A probes server answers only onceServeruns, and the App callsServeonly afterInithas returned. WhileInitruns, a probe's connection waits in the listener's backlog, or is refused if the listener isn't open yet, and the probe times out or fails. So a startup probe on the liveness path succeeds exactly whenInitis done, which is Kubernetes' own advice:A service whose
Initis slow, such as one that waits for a database or runs migrations, sets a startup probe, so liveness doesn't restart it before it has started. One that starts fast needs none.Checks: timeouts and caching
timeoutSecondscloses the connection, which cancels an HTTP request's context, and sets a gRPC call's deadline. A monitor honours the context it is given, as health's docs already ask.periodSeconds, 10s by default, so a database ping on readiness costs one ping per pod every 10 seconds. A monitor that is too expensive to check that often caches its own result, in the end user'sMonitorFunc. A cache in humus would need a setting for its age, and would answer a probe with a result older than the probe asked for. The one shared check isWatch's poller, above, because aWatchhas no requester to time it.Telemetry
s.Telemetry.LoggerProvider, under its import path,github.com/z5labs/humus/httphealthandgithub.com/z5labs/humus/grpc/grpchealth. A monitor's error is one ERROR record, in the request's context, whose body is the error and whose attributehealth.probeislivenessorreadiness. An unhealthy monitor that returns no error, such as the health Service's own state during the drain, logs nothing. The probes servers loglisteningat INFO when they start, andnet/http's error-log lines at ERROR, ashttpserverdoes. A recovered gRPC panic is logged undergrpchealth's scope, sointernal/servertakes the scope from the package that builds it.otelhttpand nootelgrpc, and extract no trace context.httpserver.Service, or the server registered on agrpcserver.Service, sits inside that Service's instrumentation, which records every request. Leaving probes out would need a filter onhttpserverandgrpcserverthat knows the probe paths. A team that doesn't want the spans serves the probes on their own port, or drops them in its sampler.net/httprecovers it, aborts the response and logs the stack at ERROR, through the error log, andinternal/serverfails the RPC withInternal. The kubelet sees a failed probe.A consumer's readiness
The Kafka proposal's open question 5 asked whether a consumer whose brokers are unreachable is ready. Its answer here is: not until it has joined its group; after that, an outage doesn't make it unready.
Nothing routes traffic to a consumer, so its readiness does one thing: it gates rollouts and disruption budgets. A Deployment's rolling update replaces old pods only as new pods turn ready. Without
Joined, a release whose brokers are misconfigured, such as a wrong address, rolls out ready pods that never consume, while franz-go keeps retrying, and the old pods that did consume are gone. WithJoinedin readiness, the new pods never turn ready, and the rollout stops at its progress deadline with the old pods still consuming.After the first join, a broker outage hits every pod at once. Turning them all unready would change nothing for traffic, and would block a rollout that might be the fix. So
Joinedstays true untilServereturns.franz-go calls
OnPartitionsAssignedafter every join, "even if nothing new is assigned", so a member with no partitions, in a group with more replicas than partitions, is joined too. The Service records the first call in the hook it already has (service.go#L491). Liveness never checks the brokers: restarting the pod doesn't bring them back.Jobs
A Kubernetes Job's pod usually has no probes: nothing routes to it, and a job that hangs is ended by its
activeDeadlineSeconds, when it sets one. Sojobgets nothing new. A long-running job that wants a liveness probe returnshttphealth.NewService's Service and a health Service next to itsjob.Service, and stays onhumus.Main. When the job finishes, the App ends, and the probes server with it.Changes to landed packages and pages
This proposal's stories make these, as the proposal that owns health's exposure:
httpserver: serving and shutdown move into an internal package of the core module, as above. Its API and behaviour don't change.grpc/internal/server:Newtakes the logger scope of the package building it.queue/kafka: theJoinedmethod. The doc's "Readiness is not decided here" is replaced by how a consumer turns ready, with an example.health's doc ("Exposing probes is Proposal: exposing health probes") points to the two packages. The root package's "Being rebuilt" names them.health.md's "Exposing the probes" section is replaced by how to expose them, with the wiring table, the paths and answers, startup probes and the manifests.grpc.md's "Health probes",kafka.md's readiness paragraph, andhttp.mdandjobs.mdlink to it. The README's/health/livenessand/health/readinessare served again.grpc.mdalready says a probe withmode: TLSreaches the end user's Service on a cluster withGRPCContainerProbeTLSon, unless the Service setsClientAuth. The new section recommends one server, withgrpchealth.NewServerregistered on the end user's Service, wherever the cluster's probes can use TLS, and the plaintext port where they can't.No lint rule, module or
.claude/backlog.jsonchange is needed: both packages are in modules that already exist.What waves 5 and 6 build on this
rest.Livenessandrest.Readiness.Design decisions
httphealthsits next tohttpserver, andgrpchealthnext togrpcserverin thegrpcmodule, so a pod that serves only HTTP probes doesn't pull in grpc-go.healthstays a standard-library leaf, as it decided.Initreturns the health Service and its exposure, as it returns every other Service. An Option that wrapsInitand adds them would have to source an address, which humus leaves to the end user, and would build a health Service the end user's monitors can't reach. A runtimeMainthat exposed probes by default would need a default port, and both server proposals decided that humus imposes none (open question 3).Probes, not*health.Service. Both packages takehealth.Probes, so an end user can expose probes built some other way. The health Service'sProbesis what the docs show, because its readiness follows the lifecycle.Init. A third probe would mean a third monitor whose meaning is "Init is done", which the lifecycle already enforces.Watchshares one poll per name. Without it, each watching client multiplies the cost of every monitor.Joinedis a method, not a type, because a method value already is aMonitorFunc, and it is API because the end user has no other way to learn that the member joined.Testing
The HTTP sketches below were prototyped in a scratch module against this repository's code: the plaintext probes Service and the handler, on a humustest Host, through a 5s drain, mounted under
/health/, withHEADand a 405, passed under-race -count=3. The gRPC answers,List, and the sharedWatchpoller were prototyped on a plain*grpc.Serveron a humustest Host, not oninternal/server, and passed the same way.Joinedwas not prototyped. Every test runs insidetesting/synctestand asserts withtestify/require.Both Services pass the contract. They keep serving after
Serve's context is done, soTestService's subtest forShutdownafterServehas returned on its own reports SKIP:Readiness through an API's drain, on a pod with a probes port:
probeis the test's helper: it makes the request, reads and closes the body, and returns the status code.gRPC health v1 on a plaintext port, with the client
grpcservertestalready provides:The tests also show the following:
health.probeset, and the request's context. An unhealthy monitor without an error logs nothing, and nothing reachesp.Stderr()./health/on anhttpserver.Servicewith a certificate fromhumustest.NewCA, throughhttpservertest.NewClient, it answers both probes, and other paths reach the end user's handler.Initreturns. AnInitthat sleeps 30s after building the probes Service gets no answer to a probe with a 1s timeout until the 30s are up, and 200 after.Watch. It sendsSERVINGat once,NOT_SERVINGwithin a second of SIGTERM, and ends withUnavailableatShutdown. Two watchers of""cause one check a second, counted by a monitor that counts its calls. An unknown name gets oneSERVICE_UNKNOWNand nothing else.Listreturns the three names.Joined. On kfake listening on the test Host,Joinedis false beforeServe, true once the group is joined, including for a member assigned no partition, and false afterServereturns. With no broker listening, it stays false while franz-go retries.CheckAddrrejects""and a port out of range without naming the field.NewServicenames each bad field, and rejects aProbeswith a nil monitor.Each package has an
Example, and both are held to the 80% coverage gate.Migration from today
GET /health/livenessand/health/readinesson everyrest.Api, 200 by defaulthttphealth.NewHandlerattached to the API's mux, orhttphealth.NewServiceon a port of its own, answering from a health Servicerest.Liveness(h),rest.Readiness(h)with a handler of the end user'shealth.Liveness(m),health.Readiness(m)with aMonitor, when building the health Servicegrpc.Api.RegisterServicehealth-checking an implementation that is ahealth.Monitorinternal/grpchealthgrpchealth.NewServer, orgrpchealth.NewServiceon a plaintext port""""is readiness, andlivenessandreadinessare namedSERVICE_UNKNOWNNOT_SERVING, and the error loggedWatchpolling every 10s, per streamhumus.Loggers.Telemetry.LoggerProviderOpen questions
httphealth.NewServiceserves plain HTTP, on code shared withhttpserver, and answers nothing but the probes. That is the gRPC decision of Proposal: gRPC services #681. The kubelet verifies no certificate over HTTPS, so TLS on a probe port proves nothing to it, and a consumer's or a job's pod would need a certificate for probes alone. The alternative keeps "TLS is required" for every HTTP port humus serves:httphealthwould have onlyNewHandler, and a probes server would behttpserver.NewServicewith a certificate. Recommendation: plain text, probes only.httpGetprobes can use HTTPS on every cluster, and itsgrpcprobes only withGRPCContainerProbeTLS. Recommendation: the docs recommend one server wherever the kubelet's probes can use TLS (HTTPS always, gRPC where the gate is on), and a probes port otherwise, which is also the only choice for a consumer's or a job's pod.Mainexpose probes by default? Health's first revision proposed it for REST. Since then,httpserverandgrpcserverrequire their address, and a default would need a probe port, which humus doesn't impose, and a health Service the end user can't add monitors to. Recommendation: no. EveryInitthat wants probes returns them, as the examples show.(*kafka.Service).Joined, true from the first join untilServereturns, as proposed? The alternatives are no monitor, so a consumer is ready as soon as it serves, or a monitor that also turns false while the member is out of its group. Recommendation:Joinedas proposed. The first gates a rollout on a working configuration, and the third makes every pod unready during a broker outage, which only blocks rollouts.Out of scope
Monitorone method./livez,/readyz,?verboseand other paths.Consuming, with the semanticsJoinedhas here.References
health/service.go,httpserver/service.go,grpc/internal/server/server.go,queue/kafka/service.gorest/api.goat v0.15.0,internal/grpchealth/server.gogrpc_health_v1OnPartitionsAssignedAll reactions