Reference Architecture¶
SolAr is intentionally flexible: roles, namespaces, registries and resources can be laid out in many ways. That flexibility can lead to choice paralysis when standing up a new environment. This page describes one opinionated, end-to-end blueprint that covers the common single-tenant-per-cluster case. It is a sound starting point that you can adapt later.
It pulls together the building blocks documented elsewhere — the role model, ReferenceGrants and SolAr Discovery — into a single, applyable layout, and links to ready-to-use example manifests.
At a glance¶
graph TB
subgraph sys["solar-system"]
SolAr["SolAr API server<br/>+ controllers"]
Disc["solar-discovery"]
end
subgraph acm["app-catalog-maintainer"]
Component["Component"]
CV["ComponentVersion"]
GrantCV["ReferenceGrant<br/>(allow Release → CV)"]
end
subgraph prov["k8s-cluster-provider"]
Registry["Registry (source, render)"]
Release["Release"]
Profile["Profile"]
RegBinding["RegistryBinding"]
GrantReg["ReferenceGrant<br/>(allow Target → Registry)"]
end
subgraph user["k8s-cluster-user"]
Target["Target"]
GrantTgt["ReferenceGrant<br/>(allow Profile/Binding → Target)"]
end
Disc -->|"discovers into"| Component
Disc -->|"discovers into"| CV
Release -.->|"references"| CV
Profile -->|"references"| Release
Profile -.->|"matches + binds"| Target
Target -.->|"references"| Registry
RegBinding -.->|"references"| Target
Dotted arrows cross a namespace boundary and therefore require a ReferenceGrant in
the namespace that owns the referenced resource.
Namespaces¶
The blueprint uses one namespace for the SolAr control plane plus one namespace per role. Keeping each role in its own namespace gives every team an isolated blast radius and a natural place to attach RBAC and ReferenceGrants.
| Namespace | Owner | Contains |
|---|---|---|
solar-system |
SolAr Operator | SolAr API server, controllers, solar-discovery |
app-catalog-maintainer |
App Catalog Maintainer | Component, ComponentVersion (the catalog) |
k8s-cluster-provider |
K8s Cluster Provider | Registry, Release, Profile, ReleaseBinding, RegistryBinding |
k8s-cluster-user |
K8s Cluster User | Target |
For a single team that owns everything, all of the SolAr resources can be collapsed into one namespace and the ReferenceGrants dropped entirely. The multi-namespace split below is the recommended default because it scales cleanly to multiple cluster users and catalog maintainers.
Roles and RBAC¶
The blueprint maps directly onto the four roles in the role model:
- App Catalog Maintainer — curates
Component/ComponentVersionresources. - K8s Cluster Provider — owns registries and drives deployments (
Release,Profile,RegistryBinding) on behalf of cluster users; manages theTargetlifecycle. - K8s Cluster User — owns the
Targetresources representing their clusters. - SolAr Operator — runs the control plane.
Apply the RBAC manifests from the role model to create the ClusterRoles,
RoleBindings and the ReferenceGrants that the cross-namespace references below rely
on. Apply order matters: the K8s Cluster Provider manifest defines a shared
solar:acm-reader ClusterRole that the K8s Cluster User manifest binds, so apply it
first.
kubectl apply -f app-catalog-maintainer.yaml
kubectl apply -f k8s-cluster-provider.yaml
kubectl apply -f k8s-cluster-user.yaml
See Roles for the full permission matrix and the manifests themselves.
Cross-namespace references and ReferenceGrants¶
Because resources are split across namespaces, references cross namespace
boundaries. A ReferenceGrant in the namespace owning the referenced resource
authorizes each crossing; without it the controllers set a NotGranted condition
and refuse to follow the reference.
| Reference (from → to) | From namespace | Grant lives in |
|---|---|---|
Release → ComponentVersion |
k8s-cluster-provider |
app-catalog-maintainer |
Target → Registry |
k8s-cluster-user |
k8s-cluster-provider |
Profile / ReleaseBinding → Target |
k8s-cluster-provider |
k8s-cluster-user |
RegistryBinding → Target |
k8s-cluster-provider |
k8s-cluster-user |
These grants are already included in the three RBAC manifests above. For the mechanics, debugging tips and the multi-tenant provisioning pattern (e.g. generating grants per tenant with Kyverno), see the ReferenceGrants guide.
SolAr Discovery integration¶
SolAr Discovery scans the source registry for OCM
components and writes Component/ComponentVersion resources directly into the
app-catalog-maintainer namespace, keeping the catalog in sync without manual
intervention. It runs in solar-system alongside the control plane.
Point discovery's created resources at the catalog namespace and scan the source registry:
# solar-discovery values.yaml
namespace: app-catalog-maintainer # where Component/ComponentVersion are created
registries:
- name: source
hostname: registry.example.com
scanInterval: 10m # full scan; add webhookPath for near-real-time
helm upgrade --install solar-discovery \
oci://ghcr.io/opendefensecloud/charts/solar-discovery \
--namespace solar-system \
--values values.yaml
Discovery is optional — the catalog can also be populated via GitOps or direct API
calls — but it is the recommended default. Enable webhook mode (webhookPath +
flavor) for near-real-time updates on registries that support it. See the
Discovery guide for all configuration options.
Resource topology¶
With RBAC, grants and discovery in place, a deployment is described by the SolAr resources below. Discovery populates the catalog; the provider creates everything else. The example manifests are concrete and applyable — adjust the hostnames, component version and labels to your environment.
Catalog (App Catalog Maintainer namespace)¶
Component and ComponentVersion are created automatically by discovery. The
ComponentVersion name is derived from the component identity and version (e.g.
ocm-demo-1-0-0); reference that name from the Release below.
Registries (K8s Cluster Provider namespace)¶
Two registries: source holds the OCM components and application images target
clusters pull; render receives the Helm charts SolAr renders.
# Registries live in the K8s Cluster Provider namespace.
# The provider owns the OCI endpoints and the credentials used to access them.
---
# Source registry: holds the OCM components (discovered into the catalog) and is
# the registry target clusters pull application images from.
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Registry
metadata:
name: source
namespace: k8s-cluster-provider
spec:
hostname: registry.example.com
# Secret name handed to target clusters so Flux can pull from this registry.
# Not read by Solar itself.
targetPullSecretName: source-pull
---
# Render registry: where Solar pushes the Helm charts it renders. Target clusters
# pull the rendered charts from here via Flux.
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Registry
metadata:
name: render
namespace: k8s-cluster-provider
spec:
hostname: render.example.com
# Secret Solar uses to push rendered charts to this registry.
solarSecretRef:
name: render-push
Release (K8s Cluster Provider namespace)¶
The Release selects a ComponentVersion from the catalog namespace (a
cross-namespace reference) and declares the target namespace on the cluster.
# A Release declares which ComponentVersion to deploy and with what configuration.
# It lives in the K8s Cluster Provider namespace and references a ComponentVersion
# in the App Catalog Maintainer namespace. The cross-namespace reference requires a
# matching ReferenceGrant in the App Catalog Maintainer namespace (see roles.md).
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Release
metadata:
name: ocm-demo
namespace: k8s-cluster-provider
spec:
componentVersionRef:
name: ocm-demo-1-0-0
namespace: app-catalog-maintainer
# uniqueName de-duplicates deployments of the same logical application.
uniqueName: ocm-demo
# Namespace the application is deployed into on the target cluster.
targetNamespace: ocm-demo
Target (K8s Cluster User namespace)¶
The Target represents a registered cluster. Its labels are matched by the
Profile; its renderRegistryRef points at the provider's render Registry.
# A Target represents a registered deployment destination (a cluster). It lives in
# the K8s Cluster User namespace; the K8s Cluster Provider manages its lifecycle.
# The labels are matched by a Profile's targetSelector. The renderRegistryRef points
# at the render Registry in the provider namespace, so a ReferenceGrant in the
# provider namespace is required (see roles.md).
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Target
metadata:
name: cluster-01
namespace: k8s-cluster-user
labels:
env: production
spec:
renderRegistryRef:
name: render
namespace: k8s-cluster-provider
userdata:
environment: production
Profile and RegistryBindings (K8s Cluster Provider namespace)¶
The Profile matches Targets by label and automatically creates a ReleaseBinding
for each — this is the trigger that starts rendering. The RegistryBindings grant
the Target permission to use the source and render registries.
# A Profile binds a Release to every Target matching its targetSelector and creates
# the ReleaseBindings automatically. It lives in the K8s Cluster Provider namespace.
# Matching Targets in the K8s Cluster User namespace requires a ReferenceGrant in
# that namespace permitting both Profile (discovery) and ReleaseBinding (rendering)
# access (see roles.md and the ReferenceGrants guide).
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Profile
metadata:
name: ocm-demo-production
namespace: k8s-cluster-provider
spec:
releaseRef:
name: ocm-demo
targetSelector:
matchLabels:
env: production
# RegistryBindings grant a Target permission to use a Registry. They live in the
# K8s Cluster Provider namespace and reference the Target in the K8s Cluster User
# namespace, so a ReferenceGrant permitting RegistryBinding access to the Target is
# required in the user namespace (see roles.md).
---
# Allow the Target to use the render registry (charts pushed by Solar).
apiVersion: solar.opendefense.cloud/v1alpha1
kind: RegistryBinding
metadata:
name: cluster-01-render
namespace: k8s-cluster-provider
spec:
targetRef:
name: cluster-01
namespace: k8s-cluster-user
registryRef:
name: render
---
# Allow the Target to use the source registry (application images pulled by Flux).
apiVersion: solar.opendefense.cloud/v1alpha1
kind: RegistryBinding
metadata:
name: cluster-01-source
namespace: k8s-cluster-provider
spec:
targetRef:
name: cluster-01
namespace: k8s-cluster-user
registryRef:
name: source
Profile vs. manual ReleaseBinding
A Profile is the recommended way to bind a Release to a fleet of Targets
because it scales to many clusters and reconciles new Targets automatically. For
a one-off deployment to a single Target you can create the ReleaseBinding
directly instead of a Profile.
End-to-end flow¶
- Catalog ingestion — The App Catalog Maintainer pushes an OCM component to the
source registry. Discovery scans it and creates
Component/ComponentVersioninapp-catalog-maintainer. - Cluster registration — The K8s Cluster Provider registers the cluster by
creating a
Targetin the user's namespace and the matchingRegistryBindings. - Deployment — The provider creates the
Release(referencing the catalogComponentVersion) and aProfile. The Profile matches the Target and creates aReleaseBinding, which drives the Target controller to render a Helm chart and push it to therenderregistry. - Reconciliation — Flux on the target cluster pulls the rendered chart from the
renderregistry and the application images from thesourceregistry, and deploys the application. - Upgrade / rollback — Point the
Releaseat a differentComponentVersion; SolAr re-renders and Flux reconciles. Deleting theProfile(orReleaseBinding) undeploys the application.
Further reading¶
- Roles — permission matrix and RBAC manifests
- ReferenceGrants — cross-namespace authorization
- SolAr Discovery — catalog population
- Architecture — system components and the rendering pipeline