# NodeTerminationPolicy CRD Reference

Full specification of the `NodeTerminationPolicy` custom resource.

## Metadata

| Property | Value |
|----------|-------|
| API Group | `citadel.dev` |
| Kind | `NodeTerminationPolicy` |
| List Kind | `NodeTerminationPolicyList` |
| Plural | `nodeterminationpolicies` |
| Singular | `nodeterminationpolicy` |
| Short Names | `ntp`, `ntpolicy` |
| Scope | Cluster |
| Version | `v1alpha1` |

## Spec

### Required Fields

| Field | Type | Description |
|-------|------|-------------|
| `spec.reason` | string | Human-readable reason for termination. Written to node labels for audit trail. |
| `spec.selector` | object | Criteria for selecting nodes to terminate. At least one selector field should be set. |

### Optional Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `spec.priority` | integer | `10` | 0-1000. Higher priority policies are evaluated first. |
| `spec.enabled` | boolean | `true` | Whether this policy is active. |
| `spec.dryRun` | boolean | `false` | If true, nodes are labeled but not drained. |

### Selector

| Field | Type | Description |
|-------|------|-------------|
| `selector.maxAgeHours` | number | Terminate nodes older than this many hours. |
| `selector.conditions` | array | Match nodes with specific Kubernetes conditions. |
| `selector.customProbes` | array | Custom probes for detecting failure modes. |
| `selector.nodeSelector` | object | Match nodes by label selector. |

#### Conditions

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `conditions[].type` | string | Yes | Condition type: `DiskPressure`, `MemoryPressure`, `Ready`, etc. |
| `conditions[].status` | string (enum) | Yes | `"True"`, `"False"`, or `"Unknown"` |

#### Custom Probes

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `customProbes[].name` | string | Yes | — | Unique probe name |
| `customProbes[].type` | string (enum) | Yes | — | `"exec"` or `"http"` |
| `customProbes[].failureThreshold` | integer | No | `3` | Consecutive failures before failed (1-100) |
| `customProbes[].periodSeconds` | integer | No | `60` | Execution interval in seconds (5-3600) |

**Exec probe config:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `exec.command` | array[string] | — | Command to execute. Non-zero exit = failure. |
| `exec.timeoutSeconds` | integer | `10` | Timeout in seconds (1-300) |

**HTTP probe config:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `http.path` | string | — | HTTP path to probe |
| `http.port` | integer | — | Port to connect to (1-65535) |
| `http.scheme` | string | `"http"` | `"http"` or `"https"` |
| `http.host` | string | `"localhost"` | Host to connect to |

#### Node Selector

Key-value map matching node labels. Example: `{"node-pool": "gpu"}`.

### Drain Configuration

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `drain.timeoutSeconds` | integer | `300` | Max time to wait for pod eviction (30-3600) |
| `drain.deleteEmptydirData` | boolean | `true` | Whether to evict pods using emptyDir volumes |
| `drain.forceAfterTimeoutSeconds` | integer | — | Force-delete pods after additional timeout (30-3600). Omit to disable force drain. |

## Status

The status subresource is managed by the operator.

| Field | Type | Description |
|-------|------|-------------|
| `status.lastEvaluated` | string (date-time) | When this policy was last evaluated |
| `status.nodesMatched` | integer | Number of nodes currently matching |
| `status.nodesTerminated` | integer | Total nodes terminated by this policy |
| `status.lastTermination` | string (date-time) | When the last node was terminated |
| `status.conditions` | array | Policy health conditions |

## Printer Columns

```
NAME                    REASON                                    PRIORITY   ENABLED   MATCHED   TERMINATED   AGE
max-age-48h             Node exceeded 48-hour maximum age         10         true      0         0            30s
```

## Examples

### Age-Based Policy

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: max-age-48h
  labels:
    citadel.dev/policy-type: age
spec:
  reason: "Node exceeded 48-hour maximum age"
  priority: 10
  enabled: true
  selector:
    maxAgeHours: 48
  drain:
    timeoutSeconds: 300
    deleteEmptydirData: true
```

### Condition-Based Policy

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: disk-pressure
  labels:
    citadel.dev/policy-type: health
spec:
  reason: "Node under disk pressure"
  priority: 100
  selector:
    conditions:
      - type: DiskPressure
        status: "True"
  drain:
    timeoutSeconds: 120
```

### Label-Selector Policy

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: dev-nodes-12h
spec:
  reason: "Development nodes recycled every 12 hours"
  priority: 20
  selector:
    maxAgeHours: 12
    nodeSelector:
      node-role: dev
      environment: development
  drain:
    timeoutSeconds: 120
```

### Combined Policy (Age + Condition + Label)

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: old-unhealthy-gpu-nodes
spec:
  reason: "GPU node is old and showing memory pressure"
  priority: 80
  selector:
    maxAgeHours: 24
    conditions:
      - type: MemoryPressure
        status: "True"
    nodeSelector:
      node.kubernetes.io/gpu: "true"
  drain:
    timeoutSeconds: 300
    forceAfterTimeoutSeconds: 600
```

### Custom Probe Policy (Exec)

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: stuck-volume-detector
  labels:
    citadel.dev/policy-type: custom-probe
spec:
  reason: "Stuck volume mount detected on node"
  priority: 150
  enabled: false  # Opt-in
  selector:
    customProbes:
      - name: stuck-volume
        type: exec
        exec:
          command: ["/bin/sh", "-c", "timeout 5 ls /var/lib/kubelet/pods/*/volumes/ > /dev/null 2>&1"]
          timeoutSeconds: 10
        failureThreshold: 3
        periodSeconds: 60
  drain:
    timeoutSeconds: 120
```

### Custom Probe Policy (HTTP)

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: gpu-health-detector
  labels:
    citadel.dev/policy-type: custom-probe
spec:
  reason: "GPU health check failed on node"
  priority: 150
  enabled: false  # Opt-in
  selector:
    customProbes:
      - name: gpu-health
        type: http
        http:
          path: /healthz
          port: 8476
        failureThreshold: 2
        periodSeconds: 30
    nodeSelector:
      node.kubernetes.io/gpu: "true"
  drain:
    timeoutSeconds: 180
```

### Dry-Run Policy

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: test-policy
spec:
  reason: "Testing new policy - dry run only"
  priority: 5
  dryRun: true
  selector:
    maxAgeHours: 1
  drain:
    timeoutSeconds: 60
```

### Force-Drain Policy

```yaml
apiVersion: citadel.dev/v1alpha1
kind: NodeTerminationPolicy
metadata:
  name: aggressive-recycle
spec:
  reason: "Aggressive node recycling with force-drain"
  priority: 50
  selector:
    maxAgeHours: 24
  drain:
    timeoutSeconds: 120
    deleteEmptydirData: true
    forceAfterTimeoutSeconds: 60  # Force-delete after 60s additional wait
```
