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-alpha4"
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 ikanos 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
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"
port: 3001
namespace: my-api
tags: [public] # "public" triggers Ingress creation
resources:
hello:
path: /hello
operations:
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
traces:
sampling: 1.0
propagation: w3c
consumes:
- namespace: upstream
type: http
baseUri: "https://api.example.com"
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 — specRef + @JsonAnySetter catch-all
│ └── SpecRef .spec.specRef
├── CapabilityStatus.java .status — phase, endpoint, conditions
├── CapabilityClassResource.java Fabric8 CR type for CapabilityClass
└── CapabilityClassSpec.java CapabilityClass spec — resources, hpa
CapabilitySpec uses @JsonAnySetter / @JsonAnyGetter as a catch-all for
all ikanos spec fields (info, capability, binds, tools, aggregates, etc.).
The reconciler parses the raw YAML as Map<String, Object> and extracts only
the eight structural fields that have Kubernetes infrastructure implications
(tier, exposes.type/port/tags, consumes.import/from, binds.location).
This makes the operator immune to ikanos schema evolution — changing resources from List to Map, adding new tool formats, restructuring aggregates — none of these require any operator change.
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 / 10 | Production SLA workloads |
dev |
128Mi / 256Mi | 100m / 250m | 1 / 1 | 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-alpha4"
info:
display: My Capability
description: "Simple hello world REST capability"
labels:
naftiko.io/tier: standard
capability:
exposes:
- type: rest
address: "0.0.0.0"
port: 3001
namespace: my-api
resources:
hello:
path: /hello
operations:
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: