Skip to content

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:

kubectl get applications -n argocd

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

cp config/capabilities/applicationset.template.yaml my-capabilities.yaml

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

kubectl apply -f my-capabilities.yaml

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

git rm -r capabilities/my-new-capability/
git commit -m "remove: my-new-capability"
git push

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:

kubectl annotate capability <name> \
  reconcile-at=$(date +%s) --overwrite -n default

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:

helm:
  values: |
    capabilityClasses:
      enabled: false

Capability CR not reconciled after push

Check the operator logs:

kubectl logs -n naftiko-system deployment/naftiko-skipper | grep <capability-name>

If the operator is in retry backoff, restart it:

kubectl delete pod -n naftiko-system -l app.kubernetes.io/name=naftiko-skipper