GitOps with ArgoCD¶
Naftiko Skipper is designed to work with ArgoCD. The operator manages the capability lifecycle inside Kubernetes — ArgoCD manages what gets deployed from Git.
How It Works¶
Developer pushes Capability CR to Git
│
▼
ArgoCD detects change and syncs to cluster
│
▼
Naftiko Skipper reconciles the Capability CR
│
▼
Running capability with full observability
Users never run kubectl apply — they just push to Git.
Platform Setup (done once by the platform team)¶
The skipper repo ships four ArgoCD Applications applied in sync-wave order:
| Wave | Application | Source |
|---|---|---|
| -1 | naftiko-crds |
config/crds/manifests/ |
| 0 | naftiko-defaults |
config/defaults/manifests/ |
| 1 | naftiko-skipper |
helm/naftiko-skipper/ |
Install ArgoCD¶
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl rollout status deployment/argocd-server -n argocd --timeout=120s
Apply the platform applications¶
# Wave -1: CRDs must exist before anything else
kubectl apply -f config/crds/application.yaml
kubectl wait --for=condition=Established \
crd/capabilities.naftiko.io \
crd/capabilityclasses.naftiko.io \
--timeout=60s
# Wave 0: CapabilityClass instances (standard, premium, dev)
kubectl apply -f config/defaults/application.yaml
# Wave 1: Operator via Helm chart
kubectl apply -f config/operator/application.yaml
Verify all three are Synced in the ArgoCD UI or:
User Setup (done once per capabilities repository)¶
Each team maintains their own Git repository of capabilities. They apply one
ApplicationSet that automatically creates one ArgoCD Application per capability
directory.
1. Copy the ApplicationSet template¶
2. Fill in the placeholders¶
sed -i \
-e 's|MY_CAPABILITIES|fleet-capabilities|g' \
-e 's|REPO_URL|https://github.com/my-team/my-capabilities.git|g' \
my-capabilities.yaml
3. Apply it¶
ArgoCD will now watch https://github.com/my-team/my-capabilities.git and
create one Application per directory under capabilities/.
Capabilities Repository Structure¶
my-capabilities/
└── capabilities/
├── hello-world/
│ ├── configmap.yaml ← ikanos spec as a Kubernetes ConfigMap
│ └── capability.yaml ← Capability CR using specRef
└── shipyard/
├── configmap.yaml
└── capability.yaml
configmap.yaml¶
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-world-spec
namespace: default
data:
capability.yaml: |
ikanos: "1.0.0-alpha3"
info:
display: Hello World
labels:
naftiko.io/tier: standard
capability:
exposes:
- type: rest
port: 3001
namespace: tutorial
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
capability.yaml¶
apiVersion: naftiko.io/v1alpha3
kind: Capability
metadata:
name: hello-world
namespace: default
labels:
naftiko.io/tier: standard
spec:
specRef:
configMap: hello-world-spec
Deploying a Capability¶
# In your capabilities repo
mkdir capabilities/my-new-capability
# create configmap.yaml and capability.yaml
git add capabilities/my-new-capability/
git commit -m "feat: add my-new-capability"
git push
ArgoCD detects the new directory and creates cap-my-new-capability. Skipper
reconciles the Capability CR. The capability is running in minutes.
Removing a Capability¶
ArgoCD prunes the Capability CR and its ConfigMap. Skipper's OwnerReference
cascade deletes the Deployment, Service, and ServiceMonitor.
Sync Waves — Why Order Matters¶
ArgoCD sync waves ensure resources are applied in dependency order:
Wave -1 CRDs installed → Kubernetes learns about Capability and CapabilityClass types
Wave 0 CapabilityClasses → standard/premium/dev tiers are available
Wave 1 Operator running → Skipper watches for Capability CRs
Wave 2+ User capabilities → CRs reconciled into running workloads
Without this ordering, applying a Capability CR before the CRD is registered
would fail with no kind "Capability" is registered.
Manual Reconcile¶
To force an immediate reconcile without changing the spec:
This is useful after updating operator env vars (e.g. OTEL endpoint) or after restoring a deleted child resource.
Troubleshooting¶
Application stuck OutOfSync
Check for SharedResourceWarning — two ArgoCD apps managing the same resource.
This typically means capabilityClasses.enabled is true in both the Helm
chart and naftiko-defaults. Set it to false in the operator application:
Capability CR not reconciled after push
Check the operator logs:
If the operator is in retry backoff, restart it: