Skip to content

Status Subresource Updates ​

The status subresource provides a dedicated endpoint for updating the status section of Kubernetes resources. This is essential for operators and controllers to report resource state without modifying the spec.

Why Use Status Subresources? ​

In Kubernetes architecture:

  • Spec = desired state (set by users/controllers)
  • Status = observed state (set by controllers)

Status subresources provide:

  • ✅ Separate permissions (RBAC) for status vs spec
  • ✅ Optimistic concurrency for status updates
  • ✅ Prevention of accidental spec changes
  • ✅ Controller status reporting without spec conflicts

Reading Status ​

Use the existing getStatus() method to read status fields:

php
$deployment = $cluster->getDeploymentByName('my-app', 'production');

$replicas = $deployment->getStatus('replicas');
$available = $deployment->getStatus('availableReplicas');
$conditions = $deployment->getStatus('conditions', []);

// Get entire status object
$statusData = $deployment->getStatusData();

Writing Status ​

Setting Status Fields Locally ​

Before updating, set status fields on the resource:

php
$deployment->setStatus('observedGeneration', 5);
$deployment->setStatus('replicas', 3);

// Or set the entire status object
$deployment->setStatusData([
    'replicas' => 3,
    'availableReplicas' => 2,
    'readyReplicas' => 2,
    'conditions' => [
        [
            'type' => 'Available',
            'status' => 'True',
            'lastTransitionTime' => date('c'),
        ],
    ],
]);

Update Status (PUT) ​

Replace the entire status section:

php
$deployment->setStatus('observedGeneration', $generation);
$deployment->updateStatus();

The updateStatus() method:

  1. Calls refreshOriginal() and refreshResourceVersion()
  2. Sends PUT request to /status endpoint
  3. Syncs the response back to the resource

JSON Patch Status (RFC 6902) ​

For surgical updates using JSON Patch operations:

php
use RenokiCo\PhpK8s\Patches\JsonPatch;

$patch = new JsonPatch();
$patch
    ->test('/status/replicas', 3)
    ->replace('/status/replicas', 5)
    ->add('/status/conditions/-', [
        'type' => 'Progressing',
        'status' => 'True',
        'lastTransitionTime' => date('c'),
        'reason' => 'NewReplicaSetCreated',
    ]);

$deployment->jsonPatchStatus($patch);

// Or use array format
$deployment->jsonPatchStatus([
    ['op' => 'replace', 'path' => '/status/replicas', 'value' => 5],
    ['op' => 'add', 'path' => '/status/conditions/0', 'value' => [...]],
]);

JSON Merge Patch Status (RFC 7396) ​

For simple merging updates using JSON Merge Patch:

php
use RenokiCo\PhpK8s\Patches\JsonMergePatch;

$patch = new JsonMergePatch([
    'status' => [
        'replicas' => 5,
        'availableReplicas' => 4,
    ],
]);

$deployment->jsonMergePatchStatus($patch);

// Or use array format directly
$deployment->jsonMergePatchStatus([
    'status' => [
        'replicas' => 5,
        'availableReplicas' => 4,
        'conditions' => [
            [
                'type' => 'Available',
                'status' => 'True',
                'lastTransitionTime' => date('c'),
            ],
        ],
    ],
]);

Operator Pattern Examples ​

Basic Controller Status Updates ​

php
use RenokiCo\PhpK8s\KubernetesCluster;

$cluster = new KubernetesCluster('http://127.0.0.1:8080');

function reconcile($cluster, $resourceName, $namespace) {
    $resource = $cluster->getConfigMapByName($resourceName, $namespace);

    // Perform reconciliation logic
    $desiredState = calculateDesiredState($resource);
    $actualState = getCurrentState($resource);

    if ($desiredState !== $actualState) {
        applyChanges($actualState, $desiredState);
    }

    // Update status to reflect observed state
    $resource->jsonMergePatchStatus([
        'status' => [
            'phase' => 'Ready',
            'observedGeneration' => $resource->getAttribute('metadata.generation'),
            'lastReconcileTime' => date('c'),
            'conditions' => [
                [
                    'type' => 'Ready',
                    'status' => 'True',
                    'lastTransitionTime' => date('c'),
                    'reason' => 'ReconciliationSucceeded',
                    'message' => 'Resource reconciled successfully',
                ],
            ],
        ],
    ]);
}

Handling Controller Conflicts ​

Controllers actively manage status, which can cause 409 Conflict errors:

php
try {
    $deployment->jsonMergePatchStatus([
        'status' => [
            'observedGeneration' => 5,
        ],
    ]);
} catch (\RenokiCo\PhpK8s\Exceptions\KubernetesAPIException $e) {
    if ($e->getCode() === 409) {
        // Conflict - controller updated status simultaneously
        // Retry with fresh resource version
        $deployment = $deployment->refresh();
        $deployment->jsonMergePatchStatus([
            'status' => [
                'observedGeneration' => 5,
            ],
        ]);
    } else {
        throw $e;
    }
}

Status Conditions Pattern ​

Kubernetes resources commonly use conditions arrays to report health:

php
function updateCondition($resource, $type, $status, $reason, $message) {
    $conditions = $resource->getStatus('conditions', []);

    // Find existing condition of this type
    $found = false;
    foreach ($conditions as $index => $condition) {
        if ($condition['type'] === $type) {
            // Update existing condition
            $conditions[$index] = [
                'type' => $type,
                'status' => $status,
                'lastTransitionTime' => date('c'),
                'reason' => $reason,
                'message' => $message,
            ];
            $found = true;
            break;
        }
    }

    // Add new condition if not found
    if (!$found) {
        $conditions[] = [
            'type' => $type,
            'status' => $status,
            'lastTransitionTime' => date('c'),
            'reason' => $reason,
            'message' => $message,
        ];
    }

    $resource->jsonMergePatchStatus([
        'status' => [
            'conditions' => $conditions,
        ],
    ]);
}

// Usage
$configMap = $cluster->getConfigMapByName('my-app-config', 'default');
updateCondition($configMap, 'Valid', 'True', 'ValidationSucceeded', 'Configuration is valid');
updateCondition($configMap, 'Synced', 'True', 'SyncSucceeded', 'Successfully synced to target');

Progressive Status Updates ​

Update status as work progresses:

php
$job = $cluster->getJobByName('backup-job', 'production');

// Starting
$job->jsonMergePatchStatus([
    'status' => [
        'phase' => 'Running',
        'startTime' => date('c'),
    ],
]);

// Progress
for ($i = 1; $i <= 10; $i++) {
    performBackupStep($i);

    $job->jsonMergePatchStatus([
        'status' => [
            'progress' => "$i/10",
            'percentComplete' => $i * 10,
        ],
    ]);

    sleep(1);
}

// Completion
$job->jsonMergePatchStatus([
    'status' => [
        'phase' => 'Completed',
        'completionTime' => date('c'),
        'progress' => '10/10',
        'percentComplete' => 100,
    ],
]);

Best Practices ​

Never Modify Spec via Status Endpoint ​

The status endpoint only updates status - spec changes are ignored:

php
// ❌ WRONG - spec changes ignored on /status endpoint
$deployment->setReplicas(5);  // Spec change
$deployment->updateStatus();   // Won't update replicas!

// ✅ CORRECT - use regular update for spec
$deployment->setReplicas(5)->update();

// ✅ CORRECT - separate spec and status updates
$deployment->setReplicas(5)->update();
$deployment->setStatus('observedGeneration', 5)->updateStatus();

Use Optimistic Locking ​

Always refresh resource version before status updates:

php
// Get fresh resource version
$resource = $resource->refresh();

// Update status
$resource->setStatus('phase', 'Ready')->updateStatus();

The updateStatus() method calls refreshResourceVersion() automatically, but for long-running operations, refresh beforehand.

Prefer Merge Patch for Simple Updates ​

For simple field updates, use merge patch instead of full replacement:

php
// ✅ GOOD - only updates specified fields
$deployment->jsonMergePatchStatus([
    'status' => [
        'availableReplicas' => 5,
    ],
]);

// ⚠️ LESS EFFICIENT - replaces entire status
$deployment->setStatusData([
    'replicas' => 5,
    'availableReplicas' => 5,
    'readyReplicas' => 5,
    // ... must include all fields
])->updateStatus();

Handle Race Conditions ​

Multiple controllers can update status simultaneously:

php
function safeUpdateStatus($resource, $statusChanges, $maxRetries = 3) {
    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        try {
            $resource->jsonMergePatchStatus([
                'status' => $statusChanges,
            ]);
            return true;
        } catch (\RenokiCo\PhpK8s\Exceptions\KubernetesAPIException $e) {
            if ($e->getCode() === 409 && $attempt < $maxRetries) {
                // Conflict - refresh and retry
                $resource = $resource->refresh();
                continue;
            }
            throw $e;
        }
    }
    return false;
}

Status Path Verification ​

Verify the correct path is being used:

php
$deployment = $cluster->deployment()
    ->setName('my-app')
    ->setNamespace('production');

echo $deployment->resourceStatusPath();
// Output: /apis/apps/v1/namespaces/production/deployments/my-app/status

Common Use Cases ​

Custom Resource Status ​

php
// Update custom resource status
$customResource = $cluster->customResource()
    ->setGroup('example.com')
    ->setVersion('v1')
    ->setKind('Database')
    ->setName('my-database')
    ->setNamespace('default');

$customResource->jsonMergePatchStatus([
    'status' => [
        'connected' => true,
        'endpoint' => 'postgresql://...',
        'version' => '14.5',
    ],
]);

Reporting Errors ​

php
try {
    performOperation();
    $resource->jsonMergePatchStatus([
        'status' => [
            'phase' => 'Succeeded',
            'lastError' => null,
        ],
    ]);
} catch (\Exception $e) {
    $resource->jsonMergePatchStatus([
        'status' => [
            'phase' => 'Failed',
            'lastError' => $e->getMessage(),
        ],
    ]);
}

Documentation for cuppett/php-k8s fork

Released under the Apache-2.0 License. Originally from renoki-co/php-k8s.