---
title: "Storage, RBAC & cluster objects — Kubernetes API for Laravel | Roundly"
description: "Namespaces, nodes, events, endpoints, volumes and claims, storage classes, roles and bindings, service accounts, quotas and limit ranges."
url: https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel/cluster-objects
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Kubernetes API for Laravel](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)

# Storage, RBAC & cluster objects

## Namespaces

```php
// Provision an isolated namespace per tenant
$cluster->namespaces()
    ->setName('tenant-acme')
    ->setLabels(['example.com/tenant' => 'acme'])
    ->create();

$tenant = $cluster->namespaces()->withName('tenant-acme')->find();
$tenant->isActive();          // status.phase === 'Active'
$tenant->isTerminating();     // status.phase === 'Terminating'
```

## Nodes, events and endpoints

Read-mostly objects come with getters for the fields you actually look at. Events also offer getType(), isNormal() and getFirstTimestamp(); endpoints getSubsets() and getSubset($index):

```php
use RoundlyConsulting\KubernetesApi\Resources\Event;

foreach ($cluster->nodes()->get() as $node) {
    $node->getName();
    $node->getInfo();              // status.nodeInfo
    $node->getCapacity();          // status.capacity
    $node->getAllocatableInfo();   // status.allocatable
    $node->getImages();            // status.images
}

// Warnings about one object, straight from the event log
$warnings = $cluster->events()
    ->setNamespace('shop')
    ->whereField('involvedObject.name', 'checkout-5f7c9d8b6-x2kqj')
    ->get()
    ->filter(fn (Event $event): bool => $event->isWarning());

foreach ($warnings as $event) {
    $event->getReason();           // e.g. 'BackOff'
    $event->getMessage();
    $event->getCount();
    $event->getLastTimestamp();    // ?Carbon
    $event->getInvolvedObject();   // kind, name, namespace…
}

// Ready addresses behind a service
$endpoints = $cluster->endpoints()->setNamespace('shop')->withName('checkout')->find();
$endpoints->getReadyAddresses();   // pod IPs
$endpoints->getPorts();            // [8080]
```

## Storage

Volumes and claims share setAccessModes(), setStorageClassName() and setCapacity($size, $measure = 'Gi'), plus isBound() and isAvailable() from status.phase. A persistent volume takes its source through setSource($type, $parameters), which writes the inline field the apiserver expects (spec.nfs, spec.csi, … — PersistentVolume::VOLUME\_SOURCES) and replaces any other source:

```php
// A claim for 10 GiB on a storage class
$cluster->persistentVolumeClaims()
    ->setNamespace('shop')
    ->setName('uploads')
    ->setAccessModes(['ReadWriteOnce'])
    ->setStorageClassName('fast-ssd')
    ->setCapacity(10, 'Gi')                    // spec.resources.requests.storage = 10Gi
    ->create();

$cluster->persistentVolumeClaims()->setNamespace('shop')->withName('uploads')->find()->isBound();

// A storage class
$cluster->storageClasses()
    ->setName('fast-ssd')
    ->setProvisioner('csi.example.com')        // magic setter -> provisioner
    ->setParameters(['type' => 'ssd'])
    ->setMountOptions(['noatime'])
    ->create();

// A pre-provisioned NFS volume
$cluster->persistentVolumes()
    ->setName('shared-media')
    ->setCapacity(100, 'Gi')                   // spec.capacity.storage = 100Gi
    ->setAccessModes(['ReadWriteMany'])
    ->setStorageClassName('nfs')
    ->setMountOptions(['nfsvers=4.1'])
    ->setSource('nfs', ['server' => 'nfs.example.com', 'path' => '/exports/media'])   // spec.nfs
    ->create();

$volume = $cluster->persistentVolumes()->withName('shared-media')->find();
$volume->getSource('nfs');   // ['server' => 'nfs.example.com', 'path' => '/exports/media']
$volume->getSource();        // ['nfs' => [...]] — the set source, keyed by type
```

A PersistentVolume has no selector API, because it has no spec.selector — a selector belongs on the claim (PersistentVolumeClaim::setSelectors()).

## RBAC and service accounts

```php
// A service account for your deploy tooling
$cluster->serviceAccounts()
    ->setNamespace('shop')
    ->setName('deployer')
    ->addImagePullSecret('registry-credentials')
    ->setAutomountServiceAccountToken(false)
    ->create();

// What it may do in the namespace
$cluster->roles()
    ->setNamespace('shop')
    ->setName('deployer')
    ->addRule(['apps'], ['deployments', 'deployments/scale'], ['get', 'list', 'patch'])
    ->addRule([''], ['pods', 'pods/log'], ['get', 'list'])
    ->create();

$cluster->roleBindings()
    ->setNamespace('shop')
    ->setName('deployer')
    ->setRoleRef('deployer')                          // kind defaults to Role
    ->addSubject('ServiceAccount', 'deployer', 'shop')
    ->addSubject('User', 'jane@example.com')
    ->create();

// Cluster-wide
$cluster->clusterRoles()
    ->setName('node-reader')
    ->addRule([''], ['nodes'], ['get', 'list', 'watch'])
    ->create();

$cluster->clusterRoleBindings()
    ->setName('deployer-node-reader')
    ->setRoleRef('node-reader')                       // always a ClusterRole
    ->addSubject('ServiceAccount', 'deployer', 'shop')
    ->create();
```

addRule($apiGroups, $resources, $verbs) appends a rule. addSubject($kind, $name, $namespace = null) gives User and Group subjects the rbac.authorization.k8s.io API group and a ServiceAccount subject none, as the apiserver requires. On a RoleBinding a ServiceAccount’s namespace may be left out — it defaults to the binding’s; on a ClusterRoleBinding it is required, and leaving it out throws InvalidResourceException. RoleBinding::setRoleRef() takes an optional kind, so a role binding can reference a ClusterRole. To mint a short-lived token for a service account, see Service-account tokens.

## Quotas and limit ranges

```php
$cluster->resourceQuotas()
    ->setNamespace('tenant-acme')
    ->setName('compute')
    ->setHard(['pods' => '20', 'requests.cpu' => '4', 'requests.memory' => '8Gi'])
    ->create();

$quota = $cluster->resourceQuotas()->setNamespace('tenant-acme')->withName('compute')->find();
$quota->getHard();   // the limits you set
$quota->getUsed();   // current usage from status.used

$cluster->limitRanges()
    ->setNamespace('tenant-acme')
    ->setName('defaults')
    ->addLimit([
        'type' => 'Container',
        'default' => ['cpu' => '500m', 'memory' => '512Mi'],
        'defaultRequest' => ['cpu' => '100m', 'memory' => '128Mi'],
    ])
    ->create();
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)
