Custom resources
Overview¶
The Capability Custom Resource Definition (CRD) is the core Kubernetes primitive of the Naftiko platform. It lets platform teams declare an integration capability as a Kubernetes object. The Naftiko Skipper operator watches these CRs and reconciles them into running workloads.
Capability CR (YAML spec)
│
▼
Naftiko Skipper (Java / JOSDK)
│ creates / updates
├─ ConfigMap (capability.yaml mounted at /data/capability.yaml)
├─ Deployment (ikanos engine — all named ports + OTEL env vars)
├─ Service (ClusterIP — one port per expose entry)
├─ Ingress (only when exposes[].tags contains "public")
└─ ServiceMonitor (only when exposes[] contains type: control)
CRD Registration¶
The CRD is registered in the cluster via capabilities.naftiko.io. Once applied, the following commands become available:
kubectl get capabilities # list all capabilities
kubectl get cap # short name
kubectl get cap -A # across all namespaces
kubectl describe cap my-cap
Key CRD properties:
| Property | Value |
|---|---|
| Group | naftiko.io |
| Version | v1alpha3 |
| Kind | Capability |
| Scope | Namespaced |
| Short name | cap |
| Category | naftiko |
Resource Structure¶
apiVersion: naftiko.io/v1alpha3
kind: Capability
metadata:
name: my-capability
namespace: default
labels:
naftiko.io/tier: standard # selects CapabilityClass
spec:
# ── Option A: inline spec ──────────────────────────────
ikanos: "1.0.0-alpha3"
info: { ... }
capability: { ... }
binds: [ ... ]
# ── Option B: specRef (recommended) ────────────────────
specRef:
configMap: my-capability-spec # ConfigMap containing the ikanos YAML
key: capability.yaml # default key — can be omitted
status: # written by the operator, read-only
phase: Running | Failed
endpoint: http://my-capability.default.svc.cluster.local:3001
conditions:
- type: Ready
status: "True"
spec Fields¶
ikanos (string)¶
Version of the Naftiko specification in use.
info (object)¶
Capability metadata. The display and description fields are required when using an inline spec.
spec:
info:
display: "My Capability"
description: "What this capability does"
tags:
- rest
- internal
labels:
naftiko.io/tier: standard # drives CapabilityClass selection
naftiko.io/domain: platform
naftiko.io/cost-center: team-x
The naftiko.io/tier label selects the CapabilityClass resource that sets pod resource requests/limits and resilience defaults. Defaults to standard if absent.
capability (object)¶
The technical configuration of what the capability exposes and consumes.
spec:
capability:
exposes:
- type: rest # rest | mcp | skill | control
address: "0.0.0.0" # always set in Kubernetes — binds to all interfaces
port: 3001
namespace: my-api
tags: [public] # "public" triggers Ingress creation
resources: [ ... ]
- type: control # observability port — /metrics, /health/*, /status
address: "0.0.0.0"
port: 9090
observability:
enabled: true
metrics:
local:
enabled: true
traces:
sampling: 1.0
propagation: w3c
consumes:
- namespace: upstream
type: http
baseUri: "https://api.example.com"
resources: [ ... ]
Every entry in exposes[] gets its own named port on both the Service and
the Deployment container spec. The control port additionally triggers a
ServiceMonitor and Prometheus pod scrape annotations.
binds (array)¶
Declares external secret sources for variable injection into the spec (e.g. API tokens, credentials). See Secrets & Binds for the full reference.
spec:
binds:
- namespace: "registry-env"
location: "file:///./shared/secrets.yaml"
keys:
API_TOKEN: "my-api-token"
The operator resolves file:// binds to a Kubernetes Secret named
{capability-name}-bind-{parent-directory} and mounts it into the engine pod.
Note: the Secret is named after the parent directory of the
locationpath, not the bindnamespace. Multiple namespaces sharing the same path are backed by one combined Secret. See Secrets & Binds for details.
specRef (object)¶
Recommended pattern. Points to a ConfigMap containing the full ikanos YAML, decoupling the spec lifecycle from the CR lifecycle.
spec:
specRef:
configMap: my-capability-spec # name of the ConfigMap in the same namespace
key: capability.yaml # key inside the ConfigMap (default: capability.yaml)
When specRef is set, the operator reads the raw YAML from the referenced
ConfigMap and writes it verbatim into the generated ConfigMap — no field is
lost or set to null.
status Fields¶
The operator writes these fields after each reconciliation. They are read-only for users.
| Field | Description | Example |
|---|---|---|
phase |
High-level state | Running, Failed |
endpoint |
Internal cluster URL (first non-control port) | http://my-cap.default.svc.cluster.local:3001 |
observedGeneration |
Last reconciled generation | 3 |
conditions[Ready] |
Standard K8s condition | True / False |
kubectl get cap my-capability -o jsonpath='{.status.phase}'
kubectl get cap my-capability -o jsonpath='{.status.endpoint}'
Java Model¶
The CRD spec is mapped to the following Java classes in the operator:
crd/
├── CapabilityResource.java Fabric8 CR type (@Group, @Version, @Kind)
├── CapabilitySpec.java .spec — typed fields + @JsonAnySetter catch-all
│ ├── InfoSpec .spec.info
│ ├── CapabilityBlock .spec.capability
│ ├── ExposeSpec .spec.capability.exposes[]
│ └── SpecRef .spec.specRef
├── CapabilityStatus.java .status — phase, endpoint, conditions
├── CapabilityClassResource.java Fabric8 CR type for CapabilityClass
└── CapabilityClassSpec.java CapabilityClass spec — resources, hpa, resilience
CapabilitySpec uses @JsonAnySetter / @JsonAnyGetter on both the root
spec and ExposeSpec to preserve unknown fields (MCP tools, descriptions,
prompts, aggregates, etc.) when the spec is serialised back to YAML for the
engine ConfigMap. Without this, any field not explicitly declared in the Java
model would be silently dropped.
CapabilityClass¶
Capabilities select a resource tier via info.labels["naftiko.io/tier"]. The
operator fetches the matching CapabilityClass cluster resource and applies
its values to the generated Deployment.
Three tiers ship by default:
| Tier | Memory req/limit | CPU req/limit | HPA min/max | Use case |
|---|---|---|---|---|
standard |
256Mi / 512Mi | 250m / 500m | 1 / 4 | Default — balanced |
premium |
512Mi / 1Gi | 500m / 1000m | 2 / 20 | Production SLA workloads |
dev |
64Mi / 128Mi | 50m / 100m | 1 / 2 | Development and staging |
If no matching CapabilityClass is found, the operator falls back to the
standard defaults.
CapabilityClasses also configure Resilience4j defaults — circuit breaker, retry, bulkhead, and rate limiter settings are applied per consumed namespace.
Printer Columns¶
kubectl get cap displays:
NAME LABEL PHASE ENDPOINT AGE
my-capability My Capability Running http://my-capability.default.svc.cluster.local:3001 5m
Quickstart¶
# 1. Save your ikanos spec
cat > my-spec.yaml << 'EOF'
ikanos: "1.0.0-alpha3"
info:
display: My Capability
labels:
naftiko.io/tier: standard
capability:
exposes:
- type: rest
address: "0.0.0.0"
port: 3001
namespace: my-api
resources: # alpha3 — keyed map
hello:
path: /hello
operations: # alpha3 — keyed map
get-hello:
method: GET
outputParameters:
- name: message
type: string
value: "Hello, World!"
- type: control
address: "0.0.0.0"
port: 9090
observability:
enabled: true
metrics:
local:
enabled: true
EOF
# 2. Upload to Kubernetes
kubectl create configmap my-spec --from-file=capability.yaml=my-spec.yaml -n default
# 3. Apply the Capability CR
cat << 'EOF' | kubectl apply -f -
apiVersion: naftiko.io/v1alpha3
kind: Capability
metadata:
name: my-capability
namespace: default
labels:
naftiko.io/tier: standard
spec:
specRef:
configMap: my-spec
EOF
# 4. Wait and test
kubectl wait pod -l naftiko.io/capability=my-capability \
--for=condition=Ready --timeout=60s -n default
kubectl port-forward svc/my-capability 3001:3001 9090:9090 -n default &
sleep 2
curl http://localhost:3001/hello
curl http://localhost:9090/metrics | grep ikanos_request_total
Reconciliation Triggers¶
The operator reconciles a Capability CR when:
- The CR is created or updated
- A child resource (ConfigMap, Deployment, Service) is modified or deleted
- The CR is annotated with
reconcile-at: