NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Kubernetes API for Laravel

Storage, RBAC & cluster objects

Namespaces

// 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):

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:

// 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

// 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', '[email protected]')
    ->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

$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

By donating, you agree to our donation terms.

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.