Repository navigation
Replies: 3 comments
Revision 1: up to date with what landedThis proposal was posted before Proposal: failure injection's revision 3, and before any of
It also names the one strip that has attributes. #869 gives a strip none, and the post gave the strip of an annotation that isn't baggage The open questions don't change. Each section below replaces or adds to the part of the same name in the post. The rest of the post stands. Proposed API
// TrustScenario carries the failure scenario that an object's annotation
// holds into each of the object's reconciles, when trust trusts the object.
// The annotation's value is W3C baggage that carries a scenario as the
// App's Decoder, fault.Settings.Decoder, reads it, such as a member of
// jsonscenario's. Only the Decoder's members join the reconcile's context.
// The reconcile's Reconciler, a Finalize's f, the controller's own writes,
// and every request and injection point they reach see the scenario.
//
// trust is asked only about an object whose annotation carries a scenario,
// and only when the App has failure injection. It is set in code, not
// sourced. A panic in trust is recovered and recorded as a panic in the
// Reconciler is, and the object isn't retried. Without TrustScenario, no
// reconcile carries a scenario.Where a reconcile's scenario comes fromThe sentence before the list becomes: When the reconcile's span starts, the controller reads the annotation from the object as the cache holds it. That is before it adds or removes its finalizer, calls a Finalize's A bullet is added after the list:
Injection pointsThe first paragraph becomes: Every request through the Client's transport evaluates one point. The injection layer sits between The table's status row gains a sentence:
A fault is a real failureThe table's "The request" row becomes:
This paragraph is added after the table: A delay that a
The two go off within microseconds of each other, and at the same instant in a TelemetryThe list becomes:
A sentence is added after the list: A strip has no attributes, as #869 has it, with one exception: a reconcile's strip for an annotation that isn't W3C baggage has The last paragraph gains a sentence: A delay that a Design decisionsA bullet is added:
TestingThe Client's points gain three bullets:
The tests against k3s gain one. client-go's fakes never see a request's context, so only a real API server shows the finalizer's apply meeting the scenario:
Changes to other filesThe second and third bullets become:
ReferencesThese are added:
Nothing else changes. |
Revision 2: the open questions answered, and a scenario goes on to the children a reconcile appliesThe answers:
All three open questions are answered. Each section below replaces or adds to the part of the same name in the post, as revision 1 left it. The rest of the post stands. SummaryThe bullet "The Client strips the scenario from every request" becomes:
BackgroundA bullet is added:
Proposed APIThe first paragraph of // TrustScenario carries the failure scenario that an object's annotation
// holds into each of the object's reconciles, when trust trusts the object.
// The annotation's value is W3C baggage that carries a scenario as the
// App's Decoder, fault.Settings.Decoder, reads it, such as a member of
// jsonscenario's. Only the Decoder's members join the reconcile's context.
// The reconcile's Reconciler, a Finalize's f, the controller's own writes,
// and every request and injection point they reach see the scenario. The
// Client carries it on to the reconciled object's children: it sets the
// annotation on every child the reconcile applies.Stripping at the API serverA sentence is added at the end: A scenario goes on to the reconciled object's children instead, in their annotation (see Carrying a scenario on to children). Carrying a scenario on to childrenThis section is new. In a reconcile that carries a scenario, the Client sets the annotation on each child the reconcile applies. It changes a request that is all of these:
The Client sets What follows:
What isn't changed:
TelemetryA sentence is added: Setting a child's annotation adds no event. The apply's span has its Design decisions"Points name the resource before the verb" drops its "Open question 2." The bullet "The Client always strips" is replaced by the last four bullets below, and the first is added:
TestingThe Client's points gain these bullets:
The tests against k3s gain two:
Changes to other files
Out of scopeThese are added:
Open questionsAll three are answered, above. Nothing else changes. |
Closing: failure injection for Kubernetes controllers has landedThe revisions' stories are done:
The revisions left no open questions. The scenario comes from an annotation the end user names, and only through The rest is out of scope or belongs to other proposals, as "Out of scope" lists:
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Note
A new feature for humus. Like Proposal: Kubernetes controllers, it isn't part of the re-evaluation in Proposal: humus on the bedrock process model, so it has no wave. It builds on two proposals, both landed and closed Resolved:
Everything it needs has landed, so its stories wait on nothing. Proposal: Kubernetes controller walkthrough depends on it.
Revision 1 brings it up to date with what landed. A delay that a
rest.Config'sTimeoutcuts short may return net/http's timeout error, as #869's revision 3 found forhttpclient. A panic intrustis recovered and never retried, as a Reconciler's is. The annotation is read before the controller adds or removes its finalizer. The injection layer sits betweenotelhttpand the token, and a watch's events land on the span in its context. The strip of an annotation that isn't baggage is named as the one strip with attributes. The open questions don't change.Revision 2 answers all three open questions. The scenario comes from an annotation the end user names (1a), and points name the resource before the verb (2a). Forwarding, in Kubernetes, is carrying a scenario on to children (3): in a reconcile that carries a scenario, the Client sets the annotation in every server-side apply of an object whose controller owner reference is the reconciled object. So an operator of several controllers carries one experiment down through its resources. Every request's baggage is still stripped, and there is no
ForwardScenario.Summary
Proposal: failure injection gives every humus server and client a part in failure injection:
A Kubernetes controller has neither part yet. Its reconciles answer no request, so nothing carries a scenario into them. Its Client talks to the API server outside every injection point. This proposal gives
kubeboth parts:kube.TrustScenario(annotation, trust)is a newPart. It reads the scenario from the annotation the end user names, for an object theirtrustfunc trusts, and puts it in the reconcile's context.Finalize'sf, the end user's own injection points, every humus client, and the controller's own writes.kube.Clientis an injection point, named after what it does to which resource, the way the API server's audit log names it:kube:deployments.apps:patch,kube:memcacheds.cache.example.com/status:patch.ForwardScenario.fault'shumus.fit.evaluationandhumus.fit.stripspan events.A scenario that aborts every apply of a Memcached's Deployment, written by the test or tool that runs the experiment:
Background
s.Faults.Eval, andfitdecodes it from the context's baggage on every call (#869's revision 2, answer 4). A server decides whether to trust a caller's scenario withTrustScenario, typed over its protocol's own request. A job trusts theBAGGAGEin its environment, because whoever sets that sets all of its settings.reconcile <Kind>, for each key the work queue hands a worker. The context it hands the Reconciler has no baggage. Sos.Faults.Evalin a Reconciler always returns the zeroFault. Anhttpclientthe Reconciler calls never sees a scenario either.After, a retry or the resync. The trigger isn't passed to the Reconciler. But the object always is, and its metadata is what Kubernetes gives for carrying data that isn't part of the spec: annotations.kube.NewClientbuilds the transport that dials through theHostand traces withotelhttp.Client.Config()hands it to every client-go client, the end user's clientsets included (#851's Connecting). It is the one seam that sees every request, ashttpclient's injection layer is for HTTP.Retry-After. It also retries a GET whose connection was reset, ended early or lost its HTTP/2 connection (request.go#L1066-L1078). An injected abort is none of those, so client-go returns it to its caller at once.fitmatches a point exactly, or by a prefix ending in*(fit.go#L64-L72). So a point name's parts should run from the broadest to the narrowest. Proposal: failure injection #869's names do:http:<host>:<port>,grpc:<service>/<method>.Proposed API
One
Partis added. Nothing else is exported, and noSettingsfield is added.TrustScenarioworks onNewControllerandNewKindControlleralike.NewControllerandNewKindControllerreport these as mistakes in their parts:trust;TrustScenarioparts.Where a reconcile's scenario comes from
After the object is decoded, and before a finalizer or the Reconciler runs, the controller reads the annotation:
humus.fit.stripevent witherror.typeanderror.message. The reconcile goes on unharmed, asfitrequires of a scenario it can't read.trustdoesn't trust: the reconcile carries no scenario. Its span gets ahumus.fit.stripevent, as a server's request does.trusttrusts: the reconcile's context gets the Decoder's members, and no other member of the annotation.RetryMaxDelay, as an outage would. Removing the annotation ends the experiment, and the reconcile that follows succeeds. A Decoder that knows expiry can expire it sooner, as Proposal: failure injection #869 left to the end user.Injection points
Every request through the Client's transport evaluates one point, after
otelhttpstarts the request's span, so thehumus.fit.evaluationevent lands on it. A request whose context carries no scenario gets the zeroFaultand no event, as #869 says of every point. The informers, leader election and the served check always run without one.The point names what the request does to which resource, as the API server's audit log and authorizer name it:
<resource>is the plural, and<group>is left out for the core group, as kubectl writes a resource:deployments.apps,secrets,memcacheds.cache.example.com.<verb>is the API server's:get,list,watch,create,update,patch,deleteordeletecollection. A server-side apply is apatch./apis, iskube:<path>:<verb>.kube:*kube:deployments.apps:*kube:deployments.apps:patchkube:memcacheds.cache.example.com/status:patchkube:memcacheds.cache.example.com:patchA point carries no namespace or object name. The scenario is already scoped to one object's reconciles, and #869's points name the dependency, not the request.
A fault is a real failure
As #869's revision 2 decided for every client, a fault emulates a failure of the dependency, and everything that meets it reacts as it would to that failure:
*fault.InjectedError*fault.InjectedErrorthat wraps the context's causeRetry-Afterhumus.kube.controller.reconcile.outcomeiserrorreconcile.durationshowsThe controller's own writes are requests like any other. An abort at
…/status:patchfails the status apply, which retries the object as any failed reconcile does. An abort at the resource'spatchfails adding or removing a finalizer.Stripping at the API server
The Client strips the Decoder's members from every request's baggage before it is sent, and the request's span gets a
humus.fit.stripevent. Every other member still goes.There is no
ForwardScenario. #869 gives every client one for a dependency that takes part in failure injection with the same codec. The API server never does: it evaluates no point of the end user's. A field that should never be set would be an API for nothing.Telemetry
Nothing new. The events are
fault's:humus.fit.evaluationon each request's span, and wherever the end user evaluates a point of their own;humus.fit.stripon a request's span, and on a reconcile's span for an annotation that is dropped.An aborted request's span has the
error.typegithub.com/z5labs/humus/fault.InjectedError, as every client's has.otelhttp'shttp.client.request.durationcounts it, andhumus.kube.controller.reconcile.durationcounts the reconcile it fails with the outcomeerror.Design decisions
jsonscenario, and imposes no annotation key either. An organisation's tooling already has its own domain for annotations, and the edge that writes scenarios is the end user's.baggageheader can be put on an object unchanged. One tool,fit.ContextWithScenarioand anEncoder, writes both.*http.Requestis forhttpserver. Who may set an annotation is decided by RBAC and admission, outside the process.trustreads what those decisions leave visible: the namespace, a label an admission policy guards, or the object's owner. nil isn't allowed: an end user who adds the Part has decided to trust someone.TrustScenariotrusts no caller. A controller that never adds it can't be put under test by anyone who can annotate its objects.Reconcile, would emulate a bug in the end user's code, which isn't a dependency's failure.fitmatches a prefix, so a rule can fail everything about one resource,kube:secrets:*. Failing one verb across every resource, such as everypatch, needs one rule per resource. Experiments on a controller are about a resource it can't write far more often than a verb that fails everywhere. Open question 2.Testing
Nothing here was prototyped, because
kubeisn't built yet (#908 to #918). The stories' tests show:Hostwith a plain HTTP handler, askube's Client tests run:errors.Asfinds an*fault.InjectedErrorin, from a clientset built fromConfig();baggageheader, with every other member kept, and thehumus.fit.stripevent;TrustScenario, insidesyncteston client-go's fakes:Finalize'sf, and a point the test evaluates withs.Faults.Eval;trustnever asked for an object without a scenario;humus.WithFaultsandjsonscenario. Annotating the Memcached with a scenario that abortskube:deployments.apps:patch:Changes to other files
kube's package doc gains failure injection:TrustScenario, the point names, what an abort and a delay do, and the strip.kube's points with the other clients', and the annotation with the servers' trust.Migration from today
Nothing migrates.
kubeis new, and has no failure injection until this proposal's stories land.Open questions
Where a reconcile's scenario comes from.
BAGGAGE. Every reconcile carries the scenario. That is the whole controller under one scenario for as long as the pod runs. It is closer to a chaos experiment, which Proposal: failure injection #869 left out of scope, and it changes only with a rollout.Recommendation: (a).
The point names.
kube:<resource>.<group>[/<subresource>]:<verb>, as proposed, so a prefix fails one resource.kube:<verb>:<resource>.<group>[/<subresource>], so a prefix fails one verb across every resource, such as every write.kube:<host>:<port>, the API server's origin, ashttpclient's points are. Every request is then one point, and an experiment can't fail one resource.Recommendation: (a).
A
ForwardScenariofor the Client.Connection, for parity with Proposal: failure injection #869's clients, default false.Recommendation: (a). The API server takes no part.
Out of scope
fit.ContextWithScenarioand anEncoder, and their RBAC and admission policies.Serve's context, which never carries a scenario. Losing the Lease, or a watch that ends, is a different kind of experiment, run against the cluster.fithas onlyAbort, and Proposal: failure injection #869 left kinds of abort out of scope.Reconcileitself, and points per namespace or object.References
humus.fit.names) and 2 (a fault is a real failure, fail-safe defaults, theInjectoronSetup)fitv0.25.0: rule matching, and z5labs/bedrock#578All reactions