> For the complete documentation index, see [llms.txt](https://docs.cloudeka.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cloudeka.ai/guidance-for-enterprise/service-limit/workload-rules.md).

# Workload Rules

Three object types are admission-checked and billed: **Pods**, **PersistentVolumeClaims (PVCs)**, and **Services of type LoadBalancer**.

## Pods

### Required: resource limits

Every container **must** end up with `resources.limits.cpu` and `resources.limits.memory` set. If you omit them, the platform **auto-fills** `cpu: 100m` / `memory: 128Mi` before your Pod is validated — so in practice you rarely need to set them yourself for small workloads, but if you set limits explicitly, both `cpu` and `memory` must be present together, or the request is rejected (see [Troubleshooting](/guidance-for-enterprise/service-limit/troubleshooting.md), [`POD6011`](/guidance-for-enterprise/service-limit/troubleshooting.md#code-pod6011)/[`POD6012`](/guidance-for-enterprise/service-limit/troubleshooting.md#code-pod6012)/[`POD6013`](/guidance-for-enterprise/service-limit/troubleshooting.md#code-pod6013)).

**Practical rule:** always set both `cpu` and `memory` limits explicitly on containers that need more than the 100m/128Mi default — don't rely on partial limits.

```yaml
resources:
  limits:
    cpu: "2"
    memory: "8Gi"
  requests:
    cpu: "2"
    memory: "8Gi"
```

### GPU requests

* Request GPUs the normal Kubernetes way (`resources.limits["nvidia.com/gpu"]`) with a node selector identifying the GPU model.
* If you request a **MIG-sliced** GPU (node selector containing `-MIG-`, e.g. `NVIDIA-H100-80GB-HBM3-MIG-1g.10gb`), the platform automatically rewrites your resource request to the correct MIG resource name and normalizes your node selector to the base GPU type — you don't need to do this yourself.
* The correct NVIDIA container runtime class is injected automatically for GPU Pods.
* A GPU model you have **no quota entry for**, or a GPU type the platform can't identify from your selector, is rejected.

### Quota/balance check

Every Pod create is checked against your quota and/or balance per the [billing type × charge type matrix](/guidance-for-enterprise/service-limit/quota-and-limits.md). `kubectl apply` on a Pod that would exceed your remaining quota or 48-hour balance runway is rejected — nothing is created.

### What you can't change after creation

Pod **updates and deletes are not quota-gated** — only creation is checked. Once a Pod is running, its `CloudekaResource` mirror (`kubectl get container`) tracks its live cost and phase automatically; deleting the Pod stops the charge.

## PersistentVolumeClaims (PVCs)

### Required fields

* `spec.storageClassName` must be set, and must be a class you have quota for (check `kubectl get rquota -o yaml` under `spec.storage`).
* `spec.resources.requests.storage` must be set.

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-data
spec:
  storageClassName: storage-nvme-c1
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 50Gi
```

### Quota/balance check

Same billing-type/charge-type matrix as Pods, applied to the requested storage size and its per-class price.

### Resizing

* **Growing** a PVC (increasing `resources.requests.storage`) is allowed, and — note — is **not** re-checked against your remaining quota/balance at resize time. Keep an eye on your `rquota` usage yourself after a large resize.
* **Shrinking** a PVC is always **rejected** (`VOL3006`). If you need less storage, delete and recreate the PVC instead (this does release/re-consume quota).

## Services (LoadBalancer / Floating IP)

Only `type: LoadBalancer` Services are checked and billed; `ClusterIP` and `NodePort` are free and unrestricted.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
```

## Check Floating IP quota and balance

On create, the platform checks your **Floating IP quota/balance** (`floatingIP` field in `rquota`) before allowing the Service.

## Wait for the external IP assignment

The external IP is assigned asynchronously by the platform's load-balancer IP pool — it won't appear instantly. Poll with:

```bash
kubectl get svc my-app -n <your-namespace> -w
```

## View the matching CloudekaFloatingIP object

Once an IP is assigned, a matching [CloudekaFloatingIP](/guidance-for-enterprise/service-limit/crds/cloudeka-floating-ip.md) object (`cfip`) appears with the same name, showing the external IP, internal ClusterIP, ports, and hourly charge:

```bash
kubectl get cfip my-app -n <your-namespace>
```

## Release the IP

Deleting the Service (or changing it away from `LoadBalancer`) releases the IP and stops the charge; the [`CloudekaFloatingIP`](/guidance-for-enterprise/service-limit/crds/cloudeka-floating-ip.md) object is removed.

{% hint style="info" %}
IP pools themselves ([CloudekaIPPool)](/guidance-for-enterprise/service-limit/crds/cloudeka-ippool.md) are platform-managed — you consume from them, you don't create or configure pools.
{% endhint %}

## Summary — what gets rejected outright

{% hint style="info" %}
For the exact error text and how to fix each one, see [Troubleshooting.](/guidance-for-enterprise/service-limit/troubleshooting.md)
{% endhint %}

<table><thead><tr><th width="257.666748046875">Action</th><th>Rejected when</th></tr></thead><tbody><tr><td>Create Pod</td><td>Missing cpu <strong>or</strong> memory limit (partial); insufficient quota/balance; unidentifiable GPU request</td></tr><tr><td>Create PVC</td><td>Missing storage class; storage class not in your quota; missing storage size request; insufficient quota/balance</td></tr><tr><td>Update PVC</td><td>Requested size is <strong>smaller</strong> than current (shrink)</td></tr><tr><td>Create Service (LoadBalancer)</td><td>Insufficient Floating-IP quota/balance</td></tr></tbody></table>
