Kubernetes multi-tenancy with projectsveltos
Tags: kubernetes, multitenancy, opensource, showdev
Published: 2023-02-09 16:47:30

Kubernetes is a complex system that relies on several components being configured correctly to have a working cluster.

Some solutions already exist to automate clusters' creation, upgrade, deletion, like for instance [ClusterAPI](https://github.com/kubernetes-sigs/cluster-api). Such solutions require the existance of a management Kubernetes cluster from where tens of other Kubernetes clusters are then programmatically created and managed.

Managing the life-cycle of Kubernetes clusters is the first step. After a Kubernetes cluster is created, an handful number of features needs to be installed (CNI for instance). [Kubernetes add-ons](https://kubernetes.io/docs/concepts/cluster-administration/addons/) are used to extend the functionality of Kubernetes clusters.

So after cluster creation, the next problem that needs to be addressed is how to programmatically manage Kubernetes add-ons in each managed cluster from the management cluster. Luckily, even in this space, there are already existing solutions. For instance, [Sveltos](https://projectsveltos.github.io/sveltos/) is an open source project to programmatically deploy Kubernetes add-ons in tens of Kubernetes clusters. 

Summarizing, using a Kubernetes cluster to manage other clusters (creation/upgrade/deletion and add-ons deployment) provides several benefits, including:

1. Centralized management: A cluster management cluster allows administrators to manage multiple clusters from a single place, making it easier to maintain consistency and reduce the risk of configuration issues.
2. Scalability: A cluster management cluster can help organizations scale their infrastructure by making it easier to create, deploy, and manage multiple clusters.
3. Better security: A cluster management cluster can be configured with security-related addons, such as network policies and secrets management, to help ensure that all managed clusters are secure.
4. Increased automation: A cluster management cluster can be integrated with a continuous integration/continuous deployment (CI/CD) pipeline, making it easier to automate the deployment of new clusters and addons, and reducing the time and effort involved in managing the infrastructure.

Problem gets more complex when different organizations end up sharing the managed Kubernetes clusters. This scenario is commonly referred to as ***multi-tenancy***. 

Common forms of multi-tenancy are:

1. share a cluster between multiple teams within an organization, each of whom may operate one or more workloads;
2. one (or more) cluster(s) fully reserved for an organization.

In this scenario we need to introduce two roles:

- **platform admin**: The role of a platform administrator is to manage and maintain the infrastructure of the managed Kubernetes clusters. This includes tasks such as setting up the cluster, managing nodes, installing and updating software components, and ensuring the overall health and security of the cluster. The platform administrator is also responsible for onboarding tenant admins and granting permissions to those;
- **tenant admin**: Has admin access to the clusters/namespaces assigned to them by the platform admin and manages tenant applications in those clusters/namespaces.

After cluster creation/upgrade/deletion and the addons deployment, the next problem to solve is how to allow platform admin to onboard tenants and programmatically grant them permissions from the management cluster.

## Sveltos addons management solution

Before we deep dive into granting permissions to tenants, lets briefly summarize [Sveltos](https://projectsveltos.github.io/sveltos/) solution to manage Kubernetes addons.

Sveltos introduces a single CRD called [***ClusterProfile***](https://projectsveltos.github.io/sveltos/configuration/#deploying-add-ons) for that.

By creating a ClusterProfile instance, we can tell Sveltos:

1. where to deploy addons (i.e, in all managed Clusters matching the ***ClusterSelector*** field);
2. list of addons to deploy (either Helm charts or Kubernetes resource YAMLs contained in referenced ConfigMaps/Secrets).

```yaml
apiVersion: config.projectsveltos.io/v1alpha1
kind: ClusterProfile
metadata:
  name: deploy-kyverno
spec:
  clusterSelector: env=fv
  syncMode: Continuous
  helmCharts:
  - repositoryURL:    https://kyverno.github.io/kyverno/
    repositoryName:   kyverno
    chartName:        kyverno/kyverno
    chartVersion:     v2.6.0
    releaseName:      kyverno-latest
    releaseNamespace: kyverno
    helmChartAction:  Install
  policyRefs:
  - name: disallow-latest-tag # referenced ConfigMap contains a Kyverno ClusterPolicy
    namespace: default
    kind: ConfigMap
```

![Sveltos addons management](https://github.com/projectsveltos/demos/raw/main/addons/sveltos_addons.gif)

With [Sveltos configuration drift detection](https://projectsveltos.github.io/sveltos/configuration_drift/) we don't have to worry about managed cluster states. Sveltos monitors the state of managed clusters and when it detects a configuration drift for one of the resource deployed because of a ClusterProfile, it re-syncs the cluster state back to the state described in the management cluster.


![Sveltos add-ons deployment with drift detection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9tcg7hds6etnpubfyw8e.gif)

## Sveltos multi-tenancy solution

Let's now go back to the multi-tenancy problem. 

We want to: 

1. allow platform admin to programmatically grant permissions to tenant admins;
2. allow tenant admins to manage applications from management cluster, making sure each tenant admin only deploys resources it has permissions to and only in the clusters/namespaces it has permission to.

***RoleRequest*** is the CRD introduced by Sveltos to allow platform admin to grant permissions to various tenant admins.

```yaml
apiVersion: lib.projectsveltos.io/v1alpha1
kind: RoleRequest
metadata:
  name: full-access
spec:
  clusterSelector: env=pre-production
  admin: eng
  roleRefs:
  - name: full-access
    namespace: default
    kind: ConfigMap
```

where:

1. **ClusterSelector** field is used to select a set of managed clusters where permissions will be granted;
2. **Admin** field selects a tenant admin (later on we will cover how this identify a tenant admin);
3. **RoleRefs** field references one of more ConfigMaps/Secrets. Each of those ConfigMaps/Secrets contains one or more [***ClusterRole/Role***](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) defining the permissions granted to the tenant admin by the platform admin.

Platform admin is the only admin in the management cluster with permissions to create/update/delete RoleRequest instances.

### Tenant admin managing applications

Once RoleRequest instances have been created, tenant admins can use Sveltos ***ClusterProfile*** CRD to instruct Sveltos on what Kubernetes resources to deploy on which clusters.

Sveltos requires that following label ```projectsveltos.io/admin-name: <admin>``` is set on a ClusterProfile instances created by a tenant admin. 
How to achieve that, is up to platform admin. 

For instance, if:

- [Kyverno](https://kyverno.io) is present in the management cluster;
- tenant admin are represented by Kubernetes **ServiceAccount**;

following Kyverno ClusterPolicy is all that is needed for label to be automatically added to each instance of a ClusterProfile:

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-labels
  annotations:
    policies.kyverno.io/title: Add Labels
    policies.kyverno.io/description: >-
      Adds projectsveltos.io/admin-name label on each ClusterProfile
      created by tenant admin. It assumes each tenant admin is
      represented in the management cluster by a ServiceAccount.
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: add-labels
    match:
      resources:
        kinds:
        - ClusterProfile
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
             projectsveltos.io/admin-name: "{{serviceAccountName}}"
```

### Examples

I have created management cluster on my laptop. It is a [Kind](https://kind.sigs.k8s.io) cluster.
In the management cluster, I have deployed both ClusterAPI (with Kind as infrastructure provider) and Sveltos.

```
kubectl get pods -A
NAMESPACE                           NAME                                                             READY   STATUS    RESTARTS   AGE
capd-system                         capd-controller-manager-59b7bdbcf9-zd4xq                         1/1     Running   0          30m
capi-kubeadm-bootstrap-system       capi-kubeadm-bootstrap-controller-manager-68f975d954-9r7lm       1/1     Running   0          30m
capi-kubeadm-control-plane-system   capi-kubeadm-control-plane-controller-manager-8476fb8676-kpnkd   1/1     Running   0          30m
capi-system                         capi-controller-manager-69fd995599-67fh5                         1/1     Running   0          30m
...
projectsveltos                      access-manager-569dd6cccd-m676t                                  2/2     Running   0          59s
projectsveltos                      classifier-manager-ddc8c756-bmxc9                                2/2     Running   0          59s
projectsveltos                      fm-controller-manager-5964fb8bff-2j2gx                           2/2     Running   0          59s
projectsveltos                      sveltoscluster-manager-75ddcf7cbd-q47hp                          2/2     Running   0          58s
```

I have one ClusterAPI powered cluster:

```
kubectl get clusters -A
NAMESPACE   NAME                          PHASE         AGE   VERSION
default     sveltos-management-workload   Provisioned   10m   v1.25.2
```

and I have one GKE cluster [registered with Sveltos](https://projectsveltos.github.io/sveltos/register-cluster/)

```
kubectl get sveltoscluster -A
NAMESPACE   NAME         AGE
gke         production   6s
```

Cluster labels are set as follow:

| Cluster                                   | Label                 |
| ----------------------------------------- | --------------------- |
| default/sveltos-management-workload       | env:development       |
| gke/production                            | env:production        |


#### Fully reserve set of cluster to a tenant

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: full-access
  namespace: default
data:
  role.yaml: |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: system-admin-full-access
    rules:
    - apiGroups: ["*"]
      resources: ["*"]
      verbs: ["*"]

---

apiVersion: lib.projectsveltos.io/v1alpha1
kind: RoleRequest
metadata:
  name: full-access
spec:
  clusterSelector: env=production
  admin: system-admin
  roleRefs:
  - name: full-access
    namespace: default
    kind: ConfigMap
```

By referencing ConfigMap __default/full-access__, above RoleRequest is reserving any cluster matching clusterSelector **env=production** to admin **system-admin**.

Using [sveltosctl](https://github.com/projectsveltos/sveltosctl) we can verify Sveltos is aware system-admin has full access to managed cluster with label env:production

```bash
./bin/sveltosctl show admin-rbac       
+-------------------------------+--------------+-----------+------------+-----------+----------------+-------+
|            CLUSTER            |    ADMIN     | NAMESPACE | API GROUPS | RESOURCES | RESOURCE NAMES | VERBS |
+-------------------------------+--------------+-----------+------------+-----------+----------------+-------+
| SveltosCluster:gke/production | system-admin | *         | *          | *         | *              | *     |
+-------------------------------+--------------+-----------+------------+-----------+----------------+-------+
```

If system-admin tries to deploy addons to such cluster, that operation will go through.

For instance, if system-admin posts following ClusterProfile

```yaml
apiVersion: config.projectsveltos.io/v1alpha1
kind: ClusterProfile
metadata:
  name: deploy-kyverno
  labels:
    projectsveltos.io/admin-name: system-admin
spec:
  clusterSelector: env=production
  syncMode: Continuous
  helmCharts:
  - repositoryURL:    https://kyverno.github.io/kyverno/
    repositoryName:   kyverno
    chartName:        kyverno/kyverno
    chartVersion:     v2.6.0
    releaseName:      kyverno-latest
    releaseNamespace: kyverno
    helmChartAction:  Install
```

we can verify Kyverno is deployed by Sveltos on behalf of system-admin:

```bash
./bin/sveltosctl show features 
+----------------+---------------+-----------+----------------+---------+-------------------------------+------------------+
|    CLUSTER     | RESOURCE TYPE | NAMESPACE |      NAME      | VERSION |             TIME              | CLUSTER PROFILES |
+----------------+---------------+-----------+----------------+---------+-------------------------------+------------------+
| gke/production | helm chart    | kyverno   | kyverno-latest | 2.6.0   | 2023-02-03 14:06:48 -0800 PST | deploy-kyverno   |
+----------------+---------------+-----------+----------------+---------+-------------------------------+------------------+
```

If system-admin tries to deploy same add-ons on clusters matching clusterSelector __env:development__  that operation will fail.

#### Share cluster between tenants

Platform admin here is granting following permissions in all clusters matching ClusterSelector __env=development__

1. Admin ***eng*** full access to namespace eng and some read permission in namespace shared-service-access;
2. Admin ***hr*** full access to namespace human-resource.

```yaml
# ConfigMap contains a Role which gives
# service read access to namespace shared-services
apiVersion: v1
kind: ConfigMap
metadata:
  name: shared-service-access
  namespace: default
data:
  role.yaml: |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: service-read-role
      namespace: shared-services
    rules:
    - apiGroups: [""]
      resources: ["services"]
      verbs: ["get,list"]

---

# ConfigMap contains a Role which gives
# full access to namespace eng
apiVersion: v1
kind: ConfigMap
metadata:
  name: eng-full-access
  namespace: default
data:
  role.yaml: |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: edit-role
      namespace: eng
    rules:
    - apiGroups: ["*"]
      resources: ["*"]
      verbs: ["*"]

---

# RoleRequest gives admin 'eng':
# - full access to namespace eng
# - some read permission in namespace shared-service-access
# in all clusters matching the label
# selector env=development
apiVersion: lib.projectsveltos.io/v1alpha1
kind: RoleRequest
metadata:
  name: eng-access
spec:
  clusterSelector: env=development
  admin: eng
  roleRefs:
  - name: eng-full-access
    namespace: default
    kind: ConfigMap
  - name: shared-service-access
    namespace: default
    kind: ConfigMap

# ConfigMap contains a Role which gives
# full access to namespace human-resource
apiVersion: v1
kind: ConfigMap
metadata:
  name: hr-full-access
  namespace: default
data:
  role.yaml: |
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: edit-role
      namespace: human-resource
    rules:
    - apiGroups: ["*"]
      resources: ["*"]
      verbs: ["*"]

---

# RoleRequest gives admin 'hr' access to namespace
# 'human-resource' in all clusters matching the label
# selector env=development
apiVersion: lib.projectsveltos.io/v1alpha1
kind: RoleRequest
metadata:
  name: hr-access
spec:
  clusterSelector: env=development
  admin: hr
  roleRefs:
  - name: hr-full-access
    namespace: default
    kind: ConfigMap
```

As before, we can use sveltosctl to verify granted permission:


```bash
./bin/sveltosctl show admin-rbac --namespace=default
+---------------------------------------------+-------+-----------------+------------+-----------+----------------+----------+
|                   CLUSTER                   | ADMIN |    NAMESPACE    | API GROUPS | RESOURCES | RESOURCE NAMES |  VERBS   |
+---------------------------------------------+-------+-----------------+------------+-----------+----------------+----------+
| Cluster:default/sveltos-management-workload | eng   | eng             | *          | *         | *              | *        |
| Cluster:default/sveltos-management-workload | eng   | shared-services |            | services  | services       | get,list |
| Cluster:default/sveltos-management-workload | hr    | human-resource  | *          | *         | *              | *        |
+---------------------------------------------+-------+-----------------+------------+-----------+----------------+----------+
```

If eng admin tries to deploy nginx in eng namespace, that will succeed.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx
  namespace: default
data:
  nginx.yaml: |
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      namespace: eng
      labels:
        app: nginx
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.14.2
            ports:
            - containerPort: 80

---

apiVersion: config.projectsveltos.io/v1alpha1
kind: ClusterProfile
metadata:
  name: deploy-nginx
  labels:
    projectsveltos.io/admin-name: eng
spec:
  clusterSelector: env=development
  syncMode: Continuous
  policyRefs:
  - name: nginx
    namespace: default
    kind: ConfigMap

```

nginx is deployed

```bash
./bin/sveltosctl show features     
+-------------------------------------+-----------------+-----------+------------------+---------+-------------------------------+------------------+
|               CLUSTER               |  RESOURCE TYPE  | NAMESPACE |       NAME       | VERSION |             TIME              | CLUSTER PROFILES |
+-------------------------------------+-----------------+-----------+------------------+---------+-------------------------------+------------------+
| default/sveltos-management-workload | apps:Deployment | eng       | nginx-deployment | N/A     | 2023-02-03 14:40:19 -0800 PST | deploy-nginx     |
+-------------------------------------+-----------------+-----------+------------------+---------+-------------------------------+------------------+
```


![Sveltos multi-tenancy summarized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5cf1xvjirdmcqikbed1.gif)

#### Contribute


![Sveltos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nx6j8w63gqn29hxqdb3.png)

[Sveltos](https://github.com/projectsveltos) is an open source project. Contributions are more welcome 🤗

If you would like to know more about it or would like to see a new feature added to sveltos, please reach to us on [slack](https://join.slack.com/t/projectsveltos/shared_invite/zt-1hraownbr-W8NTs6LTimxLPB8Erj8Q6Q). Any feedback is very much appreciated ❤️

Documentation can be found [here](https://projectsveltos.github.io/sveltos/)