GitOps Integration Guide
This guide explains how to use OptiPod with GitOps tools like ArgoCD and Flux without sync conflicts.
Overview
Section titled “Overview”OptiPod is designed to work seamlessly with GitOps workflows. The key principle: GitOps manages your desired state, OptiPod manages runtime resource optimization.
OptiPod supports two GitOps-friendly strategies:
- Webhook Strategy (Recommended): Mutates pod resources at admission time without modifying GitOps-managed manifests
- Server-Side Apply (SSA): Uses Kubernetes field ownership to manage only resource requests/limits
Why GitOps Compatibility Matters
Section titled “Why GitOps Compatibility Matters”Traditional resource optimization tools often conflict with GitOps:
Problem: GitOps tool syncs manifest → Optimizer changes resources → GitOps detects drift → GitOps reverts changes → Loop continues
OptiPod Solution: Separates concerns so both tools can coexist:
- GitOps owns: Image, replicas, environment variables, configuration
- OptiPod owns: CPU/memory requests and limits (runtime optimization)
Webhook Strategy (Recommended)
Section titled “Webhook Strategy (Recommended)”How It Works
Section titled “How It Works”The webhook strategy avoids conflicts by design:
- GitOps syncs your deployment (without resource changes)
- Kubernetes creates pods from the deployment
- OptiPod webhook intercepts pod creation
- Webhook injects optimized resources from annotations
- Pod runs with optimized resources
- GitOps sees no drift (deployment unchanged)
Configuration
Section titled “Configuration”Step 1: Enable Webhook in OptiPod Policy
apiVersion: optipod.io/v1alpha1kind: OptimizationPolicymetadata: name: production-workloadsspec: mode: Auto
selector: workloadSelector: matchLabels: optimize: "true"
metricsConfig: provider: prometheus rollingWindow: 24h percentile: P90 safetyFactor: 1.2
resourceBounds: cpu: min: "100m" max: "4000m" memory: min: "256Mi" max: "8Gi"
updateStrategy: strategy: webhook # Use webhook strategy rolloutStrategy: onNextRestart allowInPlaceResize: true updateRequestsOnly: trueStep 2: Label Your Workloads
apiVersion: apps/v1kind: Deploymentmetadata: name: web-app labels: optimize: "true" # Match policy selectorspec: replicas: 3 selector: matchLabels: app: web-app template: metadata: labels: app: web-app optimize: "true" # Important: Label pods too spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 resources: requests: cpu: "100m" memory: "128Mi"Step 3: Label Namespace for Webhook
kubectl label namespace production optipod.io/webhook=enabledHow Annotations Work
Section titled “How Annotations Work”OptiPod stores recommendations in deployment metadata annotations:
apiVersion: apps/v1kind: Deploymentmetadata: name: web-app annotations: # OptiPod stores recommendations here optipod.io/cpu-request.nginx: "250m" optipod.io/memory-request.nginx: "512Mi" optipod.io/strategy: "webhook" optipod.io/webhook-enabled: "true"spec: # ... deployment spec unchanged by OptiPod ...When pods are created:
- Webhook reads annotations from parent deployment
- Injects resources into pod spec
- Pod runs with optimized resources
- Deployment spec remains unchanged (no GitOps drift)
Advantages
Section titled “Advantages”- Zero GitOps conflicts: Deployment spec never changes
- ArgoCD/Flux agnostic: Works with any GitOps tool
- No special configuration: GitOps tools need no changes
- Audit trail: Annotations show what OptiPod recommends
- Rollback friendly: Delete annotations to disable
Server-Side Apply Strategy (Alternative)
Section titled “Server-Side Apply Strategy (Alternative)”How It Works
Section titled “How It Works”SSA uses Kubernetes field ownership to allow multiple tools to manage different fields:
Deployment: web-app├── spec.replicas [Owned by: argocd]├── spec.template.spec.containers[0]│ ├── image [Owned by: argocd]│ ├── env [Owned by: argocd]│ └── resources│ ├── requests.cpu [Owned by: optipod] ✅│ ├── requests.memory [Owned by: optipod] ✅│ ├── limits.cpu [Owned by: optipod] ✅│ └── limits.memory [Owned by: optipod] ✅Configuration
Section titled “Configuration”OptiPod Policy:
apiVersion: optipod.io/v1alpha1kind: OptimizationPolicymetadata: name: production-workloadsspec: mode: Auto
selector: workloadSelector: matchLabels: optimize: "true"
metricsConfig: provider: prometheus rollingWindow: 24h percentile: P90
resourceBounds: cpu: min: "100m" max: "4000m" memory: min: "256Mi" max: "8Gi"
updateStrategy: strategy: ssa # Use Server-Side Apply allowInPlaceResize: true allowRecreate: false updateRequestsOnly: true useServerSideApply: trueArgoCD Configuration
Section titled “ArgoCD Configuration”Option 1: ArgoCD 2.5+ (Automatic)
ArgoCD 2.5+ automatically respects SSA field ownership. No configuration needed!
Option 2: Explicit Ignore (ArgoCD < 2.5)
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-appspec: # ... other fields ... ignoreDifferences: - group: apps kind: Deployment managedFieldsManagers: - optipod - group: apps kind: StatefulSet managedFieldsManagers: - optipodOption 3: Enable SSA for ArgoCD
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-appspec: # ... other fields ... syncPolicy: syncOptions: - ServerSideApply=true - RespectIgnoreDifferences=trueFlux Configuration
Section titled “Flux Configuration”Flux v2 supports SSA natively:
apiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: web-appspec: # ... other fields ... force: false # Don't force ownership prune: trueFlux will automatically respect OptiPod’s field ownership.
Deployment Workflow
Section titled “Deployment Workflow”With ArgoCD
Section titled “With ArgoCD”Step 1: Create Application in Git
apiVersion: apps/v1kind: Deploymentmetadata: name: api-server labels: app: api-server optimize: "true"spec: replicas: 3 selector: matchLabels: app: api-server template: metadata: labels: app: api-server optimize: "true" spec: containers: - name: api image: myorg/api-server:v1.0.0 ports: - containerPort: 8080 resources: requests: cpu: "100m" memory: "128Mi"Step 2: Deploy with ArgoCD
argocd app create api-server \ --repo https://github.com/myorg/api-server \ --path k8s \ --dest-server https://kubernetes.default.svc \ --dest-namespace production \ --sync-policy automatedStep 3: Create OptiPod Policy
apiVersion: optipod.io/v1alpha1kind: OptimizationPolicymetadata: name: production-optimizerspec: mode: Auto
selector: workloadSelector: matchLabels: optimize: "true"
metricsConfig: provider: prometheus rollingWindow: 24h percentile: P90
resourceBounds: cpu: min: "100m" max: "2000m" memory: min: "128Mi" max: "4Gi"
updateStrategy: strategy: webhook # or ssa rolloutStrategy: onNextRestartkubectl apply -f optipod-policy.yamlStep 4: Verify
# Check ArgoCD sync status (should be "Synced")argocd app get api-server
# Check OptiPod statuskubectl describe optimizationpolicy production-optimizer
# View recommendations (webhook strategy)kubectl get deployment api-server -o yaml | grep "optipod.io/"
# View field ownership (SSA strategy)kubectl get deployment api-server -o yaml | grep -A 30 managedFieldsWith Flux
Section titled “With Flux”Step 1: Create Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: api-server namespace: flux-systemspec: interval: 5m path: ./k8s prune: true sourceRef: kind: GitRepository name: api-server targetNamespace: productionStep 2: Create GitRepository
apiVersion: source.toolkit.fluxcd.io/v1kind: GitRepositorymetadata: name: api-server namespace: flux-systemspec: interval: 1m url: https://github.com/myorg/api-server ref: branch: mainStep 3: Apply Flux Resources
kubectl apply -f flux/gitrepository.yamlkubectl apply -f flux/kustomization.yamlStep 4: Create OptiPod Policy
Same as ArgoCD example above.
Verification
Section titled “Verification”Check for Sync Conflicts
Section titled “Check for Sync Conflicts”ArgoCD:
argocd app get <app-name>
# Should show:# Sync Status: Synced# Health Status: HealthyFlux:
flux get kustomizations
# Should show:# NAME READY MESSAGE# api-server True Applied revision: main/abc123Verify OptiPod is Working
Section titled “Verify OptiPod is Working”# Check policy statuskubectl get optimizationpolicy -A
# Check workload annotations (webhook)kubectl get deployment <name> -n <namespace> \ -o jsonpath='{.metadata.annotations}' | jq | grep optipod.io
# Check field ownership (SSA)kubectl get deployment <name> -n <namespace> -o yaml | grep -A 30 managedFields
# Check eventskubectl get events -n <namespace> --field-selector source=optipodTest GitOps Sync
Section titled “Test GitOps Sync”Update image in Git:
spec: template: spec: containers: - name: api image: myorg/api-server:v1.1.0 # ChangedVerify:
# Wait for GitOps syncsleep 30
# Check image was updatedkubectl get deployment <name> -n <namespace> \ -o jsonpath='{.spec.template.spec.containers[0].image}'
# Check OptiPod resources are still appliedkubectl get deployment <name> -n <namespace> \ -o jsonpath='{.spec.template.spec.containers[0].resources}'Troubleshooting
Section titled “Troubleshooting”Issue: ArgoCD Shows OutOfSync
Section titled “Issue: ArgoCD Shows OutOfSync”Symptoms:
- ArgoCD marks application as OutOfSync
- Diff shows resource changes
Solutions:
For Webhook Strategy:
# Verify webhook is enabledkubectl get mutatingwebhookconfiguration optipod-webhook
# Check namespace is labeledkubectl get namespace <namespace> --show-labels | grep optipod.io/webhook
# Verify policy uses webhook strategykubectl get optimizationpolicy <policy-name> \ -o jsonpath='{.spec.updateStrategy.strategy}'For SSA Strategy:
# Upgrade ArgoCD to 2.5+ (recommended)# Or add ignoreDifferences (see SSA Configuration above)
# Verify SSA is enabledkubectl get optimizationpolicy <policy-name> \ -o jsonpath='{.spec.updateStrategy.useServerSideApply}'Issue: OptiPod Changes Reverted
Section titled “Issue: OptiPod Changes Reverted”Symptoms:
- OptiPod applies changes
- GitOps tool reverts them
Solutions:
For Webhook Strategy:
# This shouldn't happen with webhook strategy# Check that deployment spec is NOT being modifiedkubectl get deployment <name> -n <namespace> -o yaml | grep -A 10 "resources:"
# Annotations should be on metadata, not in pod templatekubectl get deployment <name> -n <namespace> \ -o jsonpath='{.metadata.annotations}' | jq | grep optipod.ioFor SSA Strategy:
# Check field ownershipkubectl get deployment <name> -n <namespace> -o yaml | grep -A 50 managedFields
# Verify OptiPod owns resource fieldskubectl get deployment <name> -n <namespace> -o yaml | \ grep -A 50 managedFields | grep -A 10 optipod
# Check ArgoCD configurationargocd app get <app-name> -o yaml | grep -A 10 ignoreDifferencesIssue: Webhook Not Mutating Pods
Section titled “Issue: Webhook Not Mutating Pods”Symptoms:
- Pods created without OptiPod resources
- No webhook events
Solutions:
# Check webhook is deployedkubectl get deployment -n optipod-system optipod-webhook
# Check webhook configurationkubectl get mutatingwebhookconfiguration optipod-webhook
# Check namespace labelkubectl label namespace <namespace> optipod.io/webhook=enabled
# Check webhook logskubectl logs -n optipod-system deployment/optipod-webhook
# Verify policy selector matches workloadkubectl get deployment <name> -n <namespace> --show-labelskubectl get optimizationpolicy <policy-name> \ -o jsonpath='{.spec.selector}' | jqIssue: Field Ownership Conflicts (SSA)
Section titled “Issue: Field Ownership Conflicts (SSA)”Symptoms:
- OptiPod logs show SSA conflicts
- Events show
SSAConflict
Solutions:
# Check which manager owns fieldskubectl get deployment <name> -n <namespace> -o yaml | \ grep -A 50 managedFields
# OptiPod uses Force=true by default to take ownership# Verify this is enabledkubectl get optimizationpolicy <policy-name> \ -o jsonpath='{.spec.updateStrategy}' | jq
# Check for conflict eventskubectl get events -n <namespace> --field-selector reason=SSAConflictBest Practices
Section titled “Best Practices”General
Section titled “General”- Start with Recommend mode: Test OptiPod before enabling Auto mode
- Use specific selectors: Target specific workloads with labels
- Set conservative bounds: Prevent unexpected resource changes
- Monitor both tools: Watch logs from both GitOps and OptiPod
- Test in staging first: Validate integration before production
Webhook Strategy
Section titled “Webhook Strategy”- Prefer webhook for GitOps: Cleanest separation of concerns
- Label namespaces: Control which namespaces webhook processes
- Use onNextRestart: Avoid forced pod disruptions
- Check annotations: Verify recommendations are stored correctly
- Monitor webhook health: Ensure webhook is running and healthy
SSA Strategy
Section titled “SSA Strategy”- Use ArgoCD 2.5+: Best SSA support
- Enable SSA in policy: Set
useServerSideApply: true - Monitor field ownership: Check managedFields regularly
- Configure ignoreDifferences: For older ArgoCD versions
- Test sync behavior: Verify no conflicts after setup
Example: Complete Setup
Section titled “Example: Complete Setup”Application Manifest (Git)
Section titled “Application Manifest (Git)”apiVersion: apps/v1kind: Deploymentmetadata: name: web-app labels: app: web-app optimize: "true"spec: replicas: 3 selector: matchLabels: app: web-app template: metadata: labels: app: web-app optimize: "true" spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 resources: requests: cpu: "100m" memory: "128Mi"ArgoCD Application
Section titled “ArgoCD Application”apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-app namespace: argocdspec: project: default source: repoURL: https://github.com/myorg/web-app targetRevision: main path: k8s destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: trueOptiPod Policy
Section titled “OptiPod Policy”apiVersion: optipod.io/v1alpha1kind: OptimizationPolicymetadata: name: web-optimizerspec: mode: Auto
selector: workloadSelector: matchLabels: optimize: "true"
metricsConfig: provider: prometheus rollingWindow: 24h percentile: P90 safetyFactor: 1.2
resourceBounds: cpu: min: "100m" max: "2000m" memory: min: "128Mi" max: "4Gi"
updateStrategy: strategy: webhook rolloutStrategy: onNextRestart allowInPlaceResize: true updateRequestsOnly: trueDeploy
Section titled “Deploy”# Deploy via ArgoCDargocd app create web-app \ --repo https://github.com/myorg/web-app \ --path k8s \ --dest-server https://kubernetes.default.svc \ --dest-namespace production \ --sync-policy automated
# Label namespace for webhookkubectl label namespace production optipod.io/webhook=enabled
# Create OptiPod policykubectl apply -f optipod-policy.yaml
# Verifyargocd app get web-appkubectl describe optimizationpolicy web-optimizerNext Steps
Section titled “Next Steps”- Creating Policies - Policy configuration guide
- Switching to Auto Mode - Enabling automatic optimization
- Troubleshooting - Common issues and solutions
- Update Strategies - Understanding webhook vs SSA
- Safety Model - Understanding safety guarantees