Skip to content
kix /docs
Install the CLI

Reference Scorecard

Cross-resource validation checks

The checks every cluster evaluation runs over the finished manifests, and what each one skips.

Every cluster evaluation runs these checks over the finished manifest list: nix build, and every kix command that evaluates a cluster, including kix build, kix check and kix deploy. They ask whether the object a name points at exists in the build, whether a selector matches a pod template in the build, and whether a custom resource depends on a declaration of its kind. They are always on, and scorecard.maxSeverity does not cap them.

This page is hand-maintained. Check kix/lib/eval/validate.nix when in doubt; kix/lib/tests/validate.nix (checks 1 to 18) and kix/lib/tests/units/lib-reference-checks.nix (checks 18 to 23) hold a failing and a passing case for each check.

#CheckSkips
1A workload’s serviceAccountName names a ServiceAccount in its namespace.default; unset
2A RoleBinding or ClusterRoleBinding roleRef names a Role or ClusterRole in the build.names starting system: or ending -psp; admin, edit, view, cluster-admin
3volumes[].configMap.name names a ConfigMap in the namespace.Secrets
4A Service selector matches at least one pod template.no selector; the control-plane scrape selectors (kube-dns, kube-scheduler and similar); a Service that declares kix.run/operand-of
5An HPA scaleTargetRef exists.
6A StatefulSet serviceName names a headless Service (clusterIP: None) in the namespace.unset
7No two resources share kind, namespace and name.
8Every resource carries app.kubernetes.io/managed-by: kix.
9A workload’s selector.matchLabels is within its pod template labels (Deployment, StatefulSet, DaemonSet, Job).
10Ingress backends name a Service in the namespace.
11Every metadata.namespace has a Namespace resource in the build._-prefixed namespaces used for cluster-level instances
12A NetworkPolicy podSelector matches at least one pod template.empty selector
13A named Service targetPort exists on a container of a selected pod.numeric ports; Services that select no pod template in the build
14A PodDisruptionBudget selector.matchLabels matches at least one pod template.no matchLabels; a PodDisruptionBudget that declares kix.run/operand-of
15A Job’s pod template labels satisfy no Service selector in its namespace, since the Service would route traffic to the Job’s pod.Services without a selector
16kix.run/rerun appears on Jobs only, with the value on-change.
17A workload’s priorityClassName names a PriorityClass in the build.system-cluster-critical, system-node-critical
18A resource of a namespaced kind sets metadata.namespace.kinds whose scope Kix does not know (see below)
19A workload’s volumes[].persistentVolumeClaim.claimName names a PersistentVolumeClaim in its namespace.
20The storageClassName of a PersistentVolumeClaim, of a StatefulSet’s volumeClaimTemplates, or of a generic ephemeral volume’s volumeClaimTemplate, names a StorageClass in the build or imported, or a PersistentVolume in the build carries it.unset and ""; classes inside operator custom resources (an SGCluster’s, a VMCluster’s)
21A resource outside the built-in API groups depends, through kix.run/depends-on, on a declaration of its group and kind that serves its version (see below).built-in groups
22A key a container reads by name exists in its Secret or ConfigMap: env[].valueFrom.secretKeyRef/configMapKeyRef, items[].key of a Secret, ConfigMap or projected volume, and the first segment of a subPath into a Secret or ConfigMap volume without items (see below).optional: true; whole use (envFrom, a volume without items)
23At most one PriorityClass sets globalDefault: true, at most one StorageClass carries storageclass.kubernetes.io/is-default-class: "true" (or the beta storageclass.beta.kubernetes.io/is-default-class, which Kubernetes still honours), and at most one IngressClass carries ingressclass.kubernetes.io/is-default-class: "true".

Check 18 covers built-in namespaced kinds, matched by API group, custom kinds whose CustomResourceDefinition in the same build declares spec.scope: Namespaced, and custom kinds a scope.mkCRDRef declares with crdScope = "Namespaced" (the default). It fails, for example, when scope.mkClusterResource builds a ConfigMap. Create such a resource with a namespaced scope (scope.mkResource) or set metadata.namespace.

Any other custom kind is checked against the cluster instead. kix deploy reads the cluster’s API discovery and refuses the whole plan before applying anything when such a resource has no namespace. kix diff against a live cluster prints the same error for each one.

Check 21: custom resources and their declarations

Section titled “Check 21: custom resources and their declarations”

The apiserver rejects an object whose kind it does not serve, and kix deploy applies a custom resource after its CRD only when the resource depends on the CRD. So every resource outside the built-in API groups ("", apps, batch, policy, autoscaling, the *.k8s.io groups Kubernetes serves, and Kix’s own group) must depend, directly or through other resources, on one of:

  • a CustomResourceDefinition in the build with its group and kind (scope.mkCRD, vendored CRDs, a chart’s own CRDs);
  • a CRD reference marker for its group and kind (scope.mkCRDRef, or a kix.mkImport { crds } entry);
  • an APIService for its group (a resource or an import marker).

The resource’s version must be one the declaration serves: a version in the CRD’s spec.versions that is not served: false, or one of the reference’s servedVersions. Building the resource with the declaring package’s builder (scope.mkCR dep.out.crds.<Kind>, or dep.out.mk<Kind> where it exists) adds the dependency.

A Helm chart’s custom resources depend on the chart’s own CRDs. A chart that renders a resource of another package’s kind (a ServiceMonitor behind a values toggle, for example) fails this check, because a chart resource cannot take a dependency: turn the toggle off and build the resource with the owner’s builder in a package of your own.

The failure names the resource, the instance that built it, and the declaration and its instance when one exists:

- [validate] ServiceMonitor apps/web (instance apps/web) does not depend on the CRD ref crd-ref-prometheus-operator-servicemonitors-monitoring-coreos-com for servicemonitors.monitoring.coreos.com (instance monitoring/prometheus-operator), which declares its kind, so the deploy can apply it before the CRD is served. ...

Keys are known for a Secret built with scope.mkSecret (its declared keys), for a scope.mkSecretRef that lists keys, and for a ConfigMap in the build (its data and binaryData). A pod that reads a key of a scope.mkSecretRef with keys = [ ] fails too: list the keys it reads, so the deploy can check the live Secret holds them.

These are not checked: a name declared with kix.untrackedRef (a Secret a controller creates); a name with no object in the build, which check 3 and the deploy own; a Secret without declared keys, such as a Helm chart’s; an object whose kix.run/cede-fields cedes data, binaryData or stringData to another manager.

A reference is looked up in the referrer’s namespace only, unless the referenced kind is a built-in cluster-scoped kind, such as a ClusterRole named by a RoleBinding or a StorageClass named by a claim. A Service, NetworkPolicy or PodDisruptionBudget selector matches pod templates in its own namespace only.

These also count as present:

  • a name wrapped in kix.untrackedRef, in the referring resource’s namespace, whatever its kind;
  • a scope.mkServiceRef or scope.mkSecretRef marker, as the Service or Secret it describes, and its declared pod selector as pods for checks 4 and 12;
  • a kix.mkImport marker with kind and out.name, as that object; for a built-in cluster-scoped kind (a StorageClass, an IngressClass) the marker counts from every namespace;
  • a custom resource named by kix.run/operand-of: <group>/<Kind>/<name> on a Service or PodDisruptionBudget, when that resource is in the build in the same namespace, for checks 4 and 14.

Check 6 reads clusterIP: None from a rendered Service, so a marker does not satisfy it.

Failures stop the evaluation and are listed together:

error: Cluster 'ludo-kind' has 1 failed assertion(s):
- [validate] Service data/postgres-metrics selector {"app":"pg"} matches no workload pod template