Flux v2’s multi-tenancy, particularly with RBAC and namespace isolation, is less about strict tenant boundaries and more about carefully managing who can see and modify what resources across which namespaces.
Let’s see it in action. Imagine we have two tenants, tenant-a and tenant-b, and we want them to manage their own applications within their designated namespaces. We’ll use Flux’s GitRepository and Kustomization resources, and control access using Kubernetes Role and RoleBinding objects.
First, we need a Git repository that contains the manifests for both tenants.
# git-repo-multitenant.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: infra-apps
namespace: flux-system # Flux controllers run here
spec:
interval: 1m
url: ssh://git@github.com/your-org/your-repo.git
ref:
branch: main
secretRef:
name: flux-ssh-key # SSH key for accessing the repo
This GitRepository is a single source of truth for all our tenant configurations. Flux will fetch this repository. The actual tenant-specific manifests will be organized within this repo, for example:
your-repo/
├── tenant-a/
│ ├── app-a/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── kustomization.yaml
└── tenant-b/
├── app-b/
│ ├── deployment.yaml
│ └── service.yaml
└── kustomization.yaml
Now, let’s define the Flux Kustomization resources. Each tenant will have their own Kustomization pointing to their specific directory in the Git repository. Crucially, these Kustomization resources will be created within the tenant’s namespace.
# kustomization-tenant-a.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: tenant-a-apps
namespace: tenant-a # Kustomization runs in the tenant's namespace
spec:
interval: 5m
path: ./tenant-a # Path within the GitRepository
prune: true
sourceRef:
kind: GitRepository
name: infra-apps # References the GitRepository defined earlier
targetNamespace: tenant-a # Resources will be applied to this namespace
# kustomization-tenant-b.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: tenant-b-apps
namespace: tenant-b # Kustomization runs in the tenant's namespace
spec:
interval: 5m
path: ./tenant-b # Path within the GitRepository
prune: true
sourceRef:
kind: GitRepository
name: infra-apps # References the GitRepository defined earlier
targetNamespace: tenant-b # Resources will be applied to this namespace
The sourceRef links these Kustomization resources back to the single GitRepository. The targetNamespace is where Flux will actually deploy the Kubernetes resources defined in the specified path. The namespace of the Kustomization itself is important for RBAC.
Now, for the RBAC. We want to grant users belonging to tenant-a the ability to manage resources only within the tenant-a namespace, and similarly for tenant-b. We’ll use Kubernetes Roles and RoleBindings.
First, a ClusterRole that defines the permissions needed to manage Flux resources (GitRepository, Kustomization) and the application resources themselves (e.g., Deployments, Services, ConfigMaps). This ClusterRole is cluster-wide but will be constrained by RoleBindings.
# cluster-role-flux-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flux-tenant-admin
rules:
- apiGroups: ["source.toolkit.fluxcd.io"]
resources: ["gitrepositories", "helmrepositories", "helmcharts", "buckets", "imagerepositories", "imagepolicies", "imagebuilds"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kustomize.toolkit.fluxcd.io"]
resources: ["kustomizations"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["helm.toolkit.fluxcd.io"]
resources: ["helmreleases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""] # Core API group
resources: ["secrets", "configmaps", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "ingresses", "pods", "persistentvolumeclaims", "events"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
This ClusterRole gives broad permissions for Flux and application resources. The isolation comes from how we bind it.
Now, RoleBindings to scope these permissions to specific namespaces.
# role-binding-tenant-a.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-a-flux-admin
namespace: tenant-a # This binding is for the tenant-a namespace
subjects:
- kind: User
name: user-a@example.com # The user we're granting access to
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: flux-tenant-admin # The ClusterRole defined above
apiGroup: rbac.authorization.k8s.io
# role-binding-tenant-b.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-b-flux-admin
namespace: tenant-b # This binding is for the tenant-b namespace
subjects:
- kind: User
name: user-b@example.com # The user we're granting access to
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: flux-tenant-admin # The ClusterRole defined above
apiGroup: rbac.authorization.k8s.io
Here’s the critical part:
KustomizationNamespace: TheKustomizationresource itself must be in the tenant’s namespace (tenant-aortenant-b). This is where the Flux controller’s reconciliation loop for thatKustomizationruns.RoleBindingNamespace: TheRoleBindingis also scoped to the tenant’s namespace. This meansuser-a@example.comcan only act asflux-tenant-adminwithin thetenant-anamespace. They can create/update/deleteKustomizationsintenant-a, and thoseKustomizationscan targettenant-a.targetNamespaceinKustomization: This explicitly tells Flux which namespace to apply the generated Kubernetes resources to. It’s the destination for the deployed application manifests.
A user like user-a@example.com can:
- Create/manage
Kustomizationresources in thetenant-anamespace. - Manage Flux
GitRepositoryresources if they haveClusterRolepermissions (often theflux-systemnamespace’sKustomizationsare managed by cluster admins). - Deploy resources only to the
tenant-anamespace, as dictated by thetargetNamespaceof theirKustomizationand theirRoleBinding’s scope. They cannot create or modify resources intenant-b.
The flux-system namespace, where the Flux controllers (source-controller, kustomize-controller, etc.) typically run, needs appropriate permissions to watch GitRepository objects and apply Kustomizations to various namespaces. This is usually handled by the flux-system’s own ClusterRoleBinding or by granting the flux-system’s ServiceAccount broad permissions. The tenant users don’t directly interact with the flux-system namespace’s resources; they manage their Kustomization objects within their own namespaces.
The key insight is that Flux’s multi-tenancy is achieved by leveraging Kubernetes RBAC to control which users can create/manage Flux resources within which namespaces, and by using the targetNamespace field in Kustomization to dictate where those Flux resources ultimately get applied.
The next hurdle you’ll likely face is managing the GitRepository and Kustomization objects themselves. If tenants shouldn’t be able to alter the GitRepository definition, you’ll need a separate ClusterRole and RoleBinding for cluster administrators to manage those, distinct from the tenant user ClusterRole.