vs alternatives¶
There are two mature tools that overlap with projection: emberstack/Reflector and Kyverno generate. Both are excellent and deployed widely. This page is about when each is the right choice.
At a glance¶
| projection | emberstack/Reflector | Kyverno generate |
|
|---|---|---|---|
| Scope of supported Kinds | Any (RESTMapper-driven) | ConfigMap, Secret only |
Any |
| Source-of-truth shape | A Projection CR per mirror |
Annotations on the source object | A cluster-wide ClusterPolicy/Policy |
| Multi-namespace fan-out | Yes, via destination.namespaceSelector (label-based) |
Yes, via reflection-auto-namespaces (regex-based) |
Yes, via policy match selectors |
| Per-mirror status / conditions | Yes (Ready, SourceResolved, DestinationWritten — rollup for selector-based) |
Partial (reflected on source annotations) | No (policy-level, not mirror-level) |
| Kubernetes Events per outcome | Projected, Updated, DestinationConflict, SourceFetchFailed, ... (per-namespace for fan-out) |
Limited | Policy-engine events |
| Conflict semantics | Refuses to overwrite unowned objects; reports DestinationConflict |
Overwrites | Configurable via synchronize, generally overwrites |
| Watch-driven propagation | Yes, dynamic per-GVK metadata-only watch | Yes | Yes |
| Admission-time source validation | Yes (pattern-validated source fields) | n/a | Yes |
| Prometheus metrics | projection_reconcile_total{result} |
Partial | Rich policy-engine metrics |
| Operational footprint | One CRD + Deployment | One CRD + Deployment | Full Kyverno control plane (several controllers) |
| Cluster-wide RBAC surface | */* (because any Kind) |
Namespace-restrictable (scope narrower) | */* (policy engine) |
Source-of-truth model¶
The biggest difference is where the rule lives.
- Reflector puts the rule on the source object: you annotate a Secret with
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"andreflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "tenant-.*". The "who's mirroring this?" question is answered by listing annotations on the source. - Kyverno puts the rule in a cluster-wide policy: one
ClusterPolicycan generate mirrored objects based on selectors, triggers, and JMESPath expressions. Reading "how did this object get here?" means finding the right policy. - projection puts the rule in a per-mirror CR: one
Projectionmaps one source to either one destination or — withdestination.namespaceSelector— to every namespace matching a label. Reading "how did this object get here?" meanskubectl describe projectionon any suspect namespace.
Consequence: projection is the easiest to diff in GitOps (each mirror is its own YAML file) and the easiest to reason about per-resource (kubectl get projections -A is the full inventory). Reflector is the easiest when you already manage the source in GitOps and don't want to create another object per destination. Kyverno is the most powerful when the mirror rule needs to match on dozens of sources at once.
Supported Kinds¶
Reflector supports ConfigMap and Secret. That's deliberate — the code is tuned for their semantics. If all you ever mirror is Secrets (the common case!), you don't need anything more.
projection and Kyverno both work on any Kind. projection does this via the RESTMapper plus Kind-specific stripping rules (currently Service, PVC, Pod; see Limitations for Kinds that may need future additions). Kyverno does it via a generic generate rule with optional variable substitution.
Status and observability¶
Per-mirror status is where the split is clearest.
projectionexposes three conditions (SourceResolved,DestinationWritten,Ready) perProjection, plus Events per state transition, plus aprojection_reconcile_total{result}counter labeled by outcome. You can alert on conflicts cheaply. See Observability.- Reflector reports reflection state via annotations on the source (e.g.
reflector.v1.k8s.emberstack.com/reflects). Useful, but not a first-class condition you can query viakubectl wait. - Kyverno has rich policy-engine metrics and reports — but they're keyed on the policy, not the individual generated object. To answer "did the
shared-tlsSecret land intenant-a?" you stillkubectl getthe destination.
If you want to put kubectl wait --for=condition=Ready in a CI pipeline or chart hook, projection is designed for that.
Conflict semantics¶
projection: refuses to overwrite destinations it doesn't own. ReportsDestinationConflicton status and as an event. This is the default and deliberate.- Reflector: generally overwrites (with some safeguards).
- Kyverno
generate: withsynchronize: true, Kyverno keeps the destination in sync; it will overwrite drift.
If you've ever had a tool overwrite an operator-owned object and break something, you know why projection takes the opposite default.
Watch model and propagation latency¶
All three are watch-driven; steady-state propagation is sub-second in all three. projection registers metadata-only watches per source GVK lazily (no cost until you create a Projection for a Kind) and uses a field indexer to map source events to Projections in O(1). In practice ~100 ms source-edit-to-destination-update on a typical cluster.
Operational surface¶
projection: one CRD, one Deployment, one container. Distroless, multi-arch. ClusterRole is*/*(see Security for narrowing recommendations).- Reflector: one CRD, one Deployment. RBAC naturally narrower (reads/writes ConfigMaps/Secrets only).
- Kyverno: multi-controller control plane, admission webhooks, report controllers. Significantly more surface, but you probably already run it.
When to pick which¶
- You already run Reflector and only mirror Secrets. Keep Reflector. The added per-mirror CR isn't worth the churn — though note that
projection'sdestination.namespaceSelectorgives you Reflector-style fan-out with per-namespace status. - You already run Kyverno and want to mirror based on source labels (one policy generating many destinations from multiple sources). Stick with Kyverno —
projectiondoesn't do source selectors yet, only destination namespace selectors. - You want the mirror rule to be a first-class, diffable, per-destination object you can
kubectl getand wait on — and you need Kinds beyond ConfigMap/Secret. That'sprojection. - You want conflict-safe-by-default (refuses to overwrite unowned objects). That's
projection; the others generally don't do this. - You want per-mirror status conditions and a Prometheus counter you can alert on. That's
projection. - Cross-cluster mirroring. None of these three today. Consider Admiralty, KubeFed (retired but concepts still inform alternatives), or Cluster API + GitOps.
The overlap isn't zero-sum — several clusters end up running projection alongside Reflector during a migration, because a per-mirror CR is a cleaner artifact to import into GitOps than a Reflector-annotated source.