Architecture¶
Reference-only inventory. One CRD: namespaced Image.
The controller answers which image refs are referenced in this namespace, and by which kinds — templates, naked Pods, and ephemeral containers on owned Pods. Not running counts, digests, or object names.
Find concrete objects with kubectl (get deploy,sts,ds,job,cronjob,pod). Inventory carries kinds only.
System overview¶
flowchart TB
subgraph NS["Namespace"]
D["Deployment / StatefulSet / DaemonSet"]
CJ["CronJob / Job"]
PO["Pods"]
end
subgraph CTRL["Image Inventory Controller"]
Q["Work queue<br/>key = namespace / _"]
R["reconcileNamespace<br/>full NS scan"]
E["ensureImage + GC"]
end
CR["Image CRs<br/>spec.image · status.kinds"]
D -->|generation change| Q
CJ -->|generation change| Q
PO -->|spec / owners / delete| Q
CR -->|create / delete only| Q
Q --> R
R --> E
E --> CR
Reconcile model¶
Events do not patch a single Image. Every watch enqueues the namespace. One reconcile rebuilds that namespace’s desired set, then creates, updates, or deletes managed Images.
sequenceDiagram
participant W as Watch
participant Q as Work queue
participant R as Reconciler
participant API as API server
W->>Q: enqueue namespace/_
Note over Q: Coalesces Pod bursts into one NS rebuild
Q->>R: Reconcile(namespace)
R->>API: List Deploy/STS/DS/CronJob/Job/Pod
R->>R: Aggregate normalized ref → kind set
R->>API: List managed Images
loop each desired ref
R->>API: CreateOrUpdate Image + status.kinds
end
R->>API: Delete managed Images not in desired set
| Choice | Why |
|---|---|
Namespace-keyed queue (name: _) |
Pod burst → one rebuild |
| Full NS scan | Simple correctness; no delta graph |
No primary For() |
All types use Watches → NS enqueue |
| Image updates ignored | Avoid loops on own status writes |
Watched: Pod, Deployment, StatefulSet, DaemonSet, Job, CronJob, Image (create/delete only). Not watched: ReplicaSet — workload templates already supply images and kind. No Pod→RS→Deploy walk.
| Target | Predicate |
|---|---|
| Pod | Container / init / ephemeral / owners / deletionTimestamp |
| Deploy / STS / DS / Job / CronJob | GenerationChangedPredicate |
| Image | Create + delete; updates ignored |
Sources → status.kinds¶
flowchart TD
W[Workload] --> T[Pod template / JobTemplate]
T --> I[ImagesFromPodSpec]
I --> K[Credit workload kind]
P[Pod] --> N{ownerReferences empty?}
N -->|yes naked| A[All containers + init + ephemeral]
A --> KP[Credit Pod]
N -->|no owned| E[Ephemeral only]
E --> KP
J[Job] --> CJ{CronJob ownerRef?}
CJ -->|no| KJ[Credit Job]
CJ -->|yes| L{live CronJob same name?}
L -->|no| KJ
L -->|yes| U{UID empty or matches?}
U -->|yes| SKIP[Skip Job]
U -->|no| KJ
| Source | Images from | Kind |
|---|---|---|
| Deployment / StatefulSet / DaemonSet | template: containers + init (+ ephemeral if present) | that kind |
| CronJob | jobTemplate pod template |
CronJob |
| Job | pod template; skipped if live CronJob same name and owner UID empty or matching | Job |
| Naked Pod | containers + init + ephemeral | Pod |
| Owned Pod | ephemeral only (kubectl debug) |
Pod |
Rules:
- Scale-to-0 still counts (template still references the image).
- Owned Pod template images do not add
Pod; parent workload already does. - CronJob > Job when a live CronJob with the same name exists and the ownerRef UID is empty or matches; otherwise Job is credited (stale UID → both kinds possible).
- Objects with
deletionTimestampcontribute nothing.
Image identity¶
- Normalize the ref (
internal/imageutil). - CR name = DNS-1123 slug + short hash of normalized ref.
- One Image per
(namespace, normalized ref). Same ref in two namespaces → two CRs.
status.kinds is a comma-separated sorted string (e.g. Deployment,Pod) so kubectl columns render. See API.
Garbage collection¶
Only Images labeled app.kubernetes.io/managed-by=image-inventory-controller participate.
After rebuild: delete managed Images not in the desired set.
- Without the label → left alone.
- With the label → adopted:
specis overwritten; orphans are garbage-collected.
Runtime¶
| Concern | Behavior |
|---|---|
--namespaces |
Comma-separated filter for cache and reconcile. Empty = all namespaces. Outside the filter, Images are not updated. Shipped RBAC is a ClusterRole. |
| Leader election | --leader-elect with lease in --leader-elect-namespace (default image-inventory-system; must match RBAC). |
| Metrics | Shipped Deployment binds 127.0.0.1:8080. |
| NetworkPolicy | Included in the default install bundle. |
Scope¶
In: referenced images from watched templates + naked/ephemeral Pods; kinds-only status; namespaced CRs; managed-by GC.
Out: live podCount, digests, object-name graphs, ReplicaSet watching, cluster-scoped Image, registry/CVE/SBOM, kubectl plugins.