Analysis
The analysis engine is the component of KubePattern that executes the logic defined in your Patterns and writes the findings as Smells. It runs as a CronJob inside your cluster and works with the permissions of its service account.
What a run does
Patterns are read from the cluster and checked by a linter. Resources are then fetched, assembled into a graph, and each pattern is evaluated against it.
For every pattern, the engine runs a well-defined pipeline:
- Candidate identification. The engine looks in the graph for the targets (the resources the smell is about) and the dependencies (the resources that give them context), by
kindandapiVersion. A target or a dependency can also stand for a set of kinds, resolved through discovery. - Filters. Candidates are narrowed to the ones that satisfy the filters of the pattern. Filters restrict the population before any relation is evaluated, so they express the scope of the pattern. When the target paths use
[@], the filters are evaluated per array element, and a target is kept with the elements that pass. - Relationship evaluation. For each surviving target, the engine evaluates the declared relationships against the candidate dependencies. With
[@], the target is defective when the relationships hold for at least one of its elements. - Results. Every target that satisfies the relationships becomes a Smell, written to the API server. Smells of that pattern whose target no longer matches are removed.
Fetching data from the cluster
Resources are fetched with the discovery and dynamic clients of client-go, which resolve kinds at run time rather than at compile time. Any kind registered with the API server, including those introduced by CRDs, is therefore reachable, and no pattern needs platform-specific code.
- Only what is needed. Only the kinds that patterns refer to are fetched, together with the owner kinds transitively reachable from them. Each kind is listed once per run, however many patterns use it.
- Kind sets. A kind wildcard (
kind: "*") or acategoryis expanded through discovery to the listable kinds it names. The kinds of afromTargetdependency are read from the targets, so they are fetched after them. - A graph built once. The fetched resources become the vertices of a graph that is built once and never modified by the analysis. The relationships in a pattern act as queries over it.
- Ownership is rebuilt. The engine follows
ownerReferencesto rebuild the chain of owners, soownsandownedBywork through intermediate resources. The walk stops at an ownership cycle.
When a kind cannot be read
KubePattern works with the permissions granted to its service account, and a failure only affects the patterns concerned. If a kind cannot be listed, for example because its CRD is not installed or because access to it is forbidden, every pattern that needs that kind, directly or as an owner, is skipped with a warning, and the others are still analysed.
| Situation | What happens |
|---|---|
| A pattern is malformed | It is logged and skipped. |
| A kind is not installed, or access is forbidden | The patterns that need it are skipped with a warning. |
A kind: "*" names an unknown group version | The pattern is skipped, as for a missing CRD. |
A category matches no kind | The pattern runs, and the category has no instances. As a dependency under matchNone, that reports every target. |
A fromTarget dependency names a kind the server does not serve | That kind has no instances, and a warning is logged. |
| A pattern is skipped | Its existing Smells are kept: an unreadable kind is not reported as "resolved". |
| A pattern is deleted | Its Smells are removed at the end of the next run. |
| Two runs overlap | Each pattern removes only its own stale Smells, so neither run deletes the other's findings. |
next release Keeping the Smells of skipped patterns, and pruning one pattern at a time, are on the main branch. Earlier versions remove every Smell that a run did not refresh, including those of a pattern that was skipped.
Filters
The filtering system navigates the manifests of resources.
- Combinators. Conditions combine with
matchAll(all must hold),matchAny(at least one must) andmatchNone(none may). - Paths. A filter can look at any path of the object (for example
metadata.labels), and[*]ranges over arrays such as the containers of a Pod.[0]picks one element, a quoted key such asmetadata.labels['app.kubernetes.io/name']reads a key with dots, and[@]binds one element at a time. See Paths. - Operators. Besides equality, filters check that a field exists or is empty, compare numbers, and compare the size of arrays. The full list is in the Pattern reference.
- Typed equality.
EQUALScompares booleans and numbers by their string form, andvaluesFromcompares a field with another field of the same resource.
Relationships
After the targets and dependencies are identified, the resolver checks how they relate. It interprets the relationships block of the pattern.
- Like filters, relationships use
matchAll,matchAnyandmatchNone. - A relationship holds for a target when the logic holds against at least one dependency instance. Under
matchNone, that statement is negated, which is how orphans and unused definitions are expressed. - When a dependency has no instances at all,
matchNoneis satisfied and every target yields a finding. - Custom relationships compare a field of the target with a field of the dependency. They hold when the two sets of extracted values have a non-empty intersection, and wildcards let one criterion range over a whole array.
[@]makes all the criteria read the same array element, a default stands in for an omitted field, and a transform extracts the part of a string to compare. - Ownership relationships (
owns,ownedBy) followownerReferencestransitively, and need no criteria. - Label selector relationships (
selects,selectedBy) match the selector of one resource against the labels of the other. See Label selectors.
Output
The result of a run is a set of Smell custom resources.
- Dynamic messages. Placeholders in the message of the pattern are replaced with the data of the offending resource, so a finding explains itself.
- Stable identity. The name of a Smell combines the pattern name and the UID of its target, so repeated runs update the existing Smell instead of creating a new one.
- Saved details. A Smell carries the category, the severity, a link to documentation and the exact target that caused it.
- A lifecycle. A Smell remembers when it was first observed and can wait for a minimum duration before it turns
Active. See Lifecycle.
Performance
A run lists each kind once and writes one Smell per finding, so the number of requests grows with the number of findings rather than with the number of patterns.
- Rate limit. The Kubernetes client is throttled on the client side, and the
client-godefault of 5 requests per second makes a run slow when many Smells are written. next release The chart raises the limit to 50 requests per second with a burst of 100, andanalysis.client.qpsandburstchange it. - Memory grows linearly with the fetched objects, at about 9 KiB each in KubePattern's evaluation.
- Matching time grows with the product of the number of targets and dependencies, since there is no index. It is not noticeable until the cluster holds tens of thousands of objects. The dependency side of each relationship is now extracted once per pattern instead of once per target, which cut the matching time on 20,000 objects from 33 to 3.3 seconds in KubePattern's evaluation.
Each run is bounded by a five-minute deadline, and the chart's CronJob does not allow two runs at once.