Skip to content

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.

How a KubePattern run moves data Patterns and live resources are read from the cluster. The engine lints the patterns, fetches the kinds they name, builds a graph, evaluates the relationships and writes Smell resources back to the same cluster. Kubernetes clusterPatternskind: PatternLive resourcesany kind, CRDs includedKubePattern engine · runs as a CronJob1Lint patternsa malformed pattern is skipped2Fetch kindsdiscovery + dynamic client3Build the graphonce per run, read-only4Evaluaterelationships as queries5Write smellscreate, update, pruneSmellskind: Smellreadlistwrite

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:

  1. 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 kind and apiVersion. A target or a dependency can also stand for a set of kinds, resolved through discovery.
  2. 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.
  3. 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.
  4. 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 a category is expanded through discovery to the listable kinds it names. The kinds of a fromTarget dependency 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 ownerReferences to rebuild the chain of owners, so owns and ownedBy work 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.

SituationWhat happens
A pattern is malformedIt is logged and skipped.
A kind is not installed, or access is forbiddenThe patterns that need it are skipped with a warning.
A kind: "*" names an unknown group versionThe pattern is skipped, as for a missing CRD.
A category matches no kindThe 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 serveThat kind has no instances, and a warning is logged.
A pattern is skippedIts existing Smells are kept: an unreadable kind is not reported as "resolved".
A pattern is deletedIts Smells are removed at the end of the next run.
Two runs overlapEach 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) and matchNone (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 as metadata.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. EQUALS compares booleans and numbers by their string form, and valuesFrom compares 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, matchAny and matchNone.
  • 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, matchNone is 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) follow ownerReferences transitively, 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-go default 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, and analysis.client.qps and burst change 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.