ADR-049: ArgoCD Integration Strategy for GitOps Workflows¶
Status: Accepted Date: 2026-01-24 Updated: 2026-01-24 Deciders: Tosin Akinosho Context: GitOps Workflow Coordination and Model Serving Integration
Status¶
Accepted
Context and Problem Statement¶
The jupyter-notebook-validator-operator is a mature, production-ready operator with comprehensive ML model validation capabilities. However, it currently has zero ArgoCD integration, creating coordination gaps in GitOps workflows, especially for KServe model serving where notebook completion should trigger downstream resources.
Current Operational Issues¶
Cluster Status: - InferenceServices: ½ operational (anomaly-detector ✅, predictive-analytics ❌) - NotebookValidationJobs: 30/32 succeeded (93.75% success rate) - Root Issue: Model training notebooks complete, but InferenceServices don't know when to reload models - Manual intervention required: delete pods to trigger model reload
ArgoCD Integration Gaps: - No Application status reporting - No sync wave annotations/awareness - No ArgoCD event/notification integration - No automatic downstream resource triggering - No health assessment customization - No synchronization with Argo Application lifecycle
Decision Drivers¶
- GitOps Workflow Completeness: Current manual intervention breaks declarative GitOps principles
- Model Lifecycle Coordination: Need automatic synchronization between model training and serving
- Operator Maturity: 47 ADRs, production-ready operator needs enterprise GitOps features
- Platform Detection Exists:
pkg/platform/detector.goalready provides extensibility foundation - ADR-007 Vision: Explicitly mentions "Integrate with ArgoCD, Flux, Tekton" as strategic goal
- Production Impact: Real deployment issues in openshift-aiops-platform requiring this integration
Problems Addressed¶
Problem 1: No Coordination Between Notebooks and InferenceServices¶
Scenario:
1. NotebookValidationJob trains model → saves to PVC → completes successfully
2. InferenceService is already running with old model
3. InferenceService has no idea a new model is available
4. Manual intervention required: kubectl delete pod to trigger reload
Impact: - Models don't auto-update after training - Requires manual kubectl commands - Breaks GitOps declarative workflow
Problem 2: ArgoCD Application Shows "Synced" But Models Are Stale¶
Scenario: 1. ArgoCD syncs → deploys InferenceService 2. Notebook runs 10 minutes later → trains new model 3. ArgoCD still shows "Synced" (technically correct for K8s resources) 4. But model in production is outdated
Impact: - False sense of "everything is current" - No visibility into model freshness - Drift between code sync and model sync
Problem 3: No Dependency Ordering in GitOps¶
Scenario: 1. Want to ensure: Notebook completes → Model validated → InferenceService deployed 2. Current options: - Manual sync wave ordering (brittle) - Wait/sleep commands (timing issues) - Manual intervention (not GitOps)
Impact: - Race conditions - Failed deployments if models not ready - Cannot express "deploy InferenceService AFTER notebook succeeds"
Problem 4: No Observability in ArgoCD UI¶
Scenario:
1. User checks ArgoCD Application health
2. Sees InferenceService status but not NotebookValidationJob status
3. Can't tell if model training is in progress/failed
4. Must switch to oc get notebookvalidationjob in separate terminal
Impact: - Poor UX for operators - Hidden failures in notebook validation - No single pane of glass
Proposed Features¶
Feature 1: ArgoCD Health Assessment Hook ⭐ HIGH PRIORITY¶
What: Custom health check that reports NotebookValidationJob status to ArgoCD
Why: ArgoCD Application health would include notebook validation status
Implementation: ArgoCD supports custom health checks via Lua scripts in ConfigMap. The operator will document the health assessment configuration:
# ConfigMap: argocd-cm (in argocd namespace)
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
resource.customizations.health.mlops.mlops.dev_NotebookValidationJob: |
hs = {}
if obj.status == nil or obj.status.phase == nil then
hs.status = "Progressing"
hs.message = "Initializing notebook validation"
return hs
end
if obj.status.phase == "Succeeded" then
hs.status = "Healthy"
hs.message = "Notebook validation succeeded"
if obj.status.completionTime ~= nil then
hs.message = hs.message .. " at " .. obj.status.completionTime
end
elseif obj.status.phase == "Failed" then
hs.status = "Degraded"
hs.message = obj.status.message or "Notebook validation failed"
if obj.status.retryCount ~= nil then
hs.message = hs.message .. " (retry " .. obj.status.retryCount .. "/3)"
end
elseif obj.status.phase == "ValidationRunning" then
hs.status = "Progressing"
hs.message = "Validating notebook..."
if obj.status.validationPodName ~= nil then
hs.message = hs.message .. " (pod: " .. obj.status.validationPodName .. ")"
end
elseif obj.status.phase == "Building" or obj.status.phase == "BuildComplete" then
hs.status = "Progressing"
hs.message = "Building validation environment"
elseif obj.status.phase == "Initializing" or obj.status.phase == "Pending" then
hs.status = "Progressing"
hs.message = "Initializing..."
else
hs.status = "Progressing"
hs.message = "Phase: " .. obj.status.phase
end
return hs
Status Mapping:
- Phase Succeeded → ArgoCD Healthy
- Phase Failed → ArgoCD Degraded
- Phase ValidationRunning, Building, BuildComplete, Initializing, Pending → ArgoCD Progressing
Benefit: - ArgoCD UI shows notebook status - Application health reflects model training state - Operators see issues immediately in single pane of glass
Implementation Complexity: LOW (documentation only, no code changes) Estimated Effort: 1 week (testing and documentation)
Feature 2: Post-Success Resource Hook ⭐ HIGH PRIORITY¶
What: Operator annotation to trigger resource updates when notebook succeeds
Why: Automatically restart InferenceServices when new models are trained
Implementation: Add annotation-based triggering system to controller:
apiVersion: mlops.mlops.dev/v1alpha1
kind: NotebookValidationJob
metadata:
name: predictive-analytics-kserve-validation
annotations:
mlops.dev/on-success-trigger: |
- apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
name: predictive-analytics
namespace: self-healing-platform
action: restart
- apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
name: anomaly-detector
namespace: self-healing-platform
action: refresh
spec:
# ... existing spec
Controller Logic:
// In notebookvalidationjob_controller.go
func (r *NotebookValidationJobReconciler) handleSuccess(ctx context.Context, job *mlopsv1alpha1.NotebookValidationJob) error {
// Parse on-success-trigger annotation
triggers, err := parseSuccessTriggers(job.Annotations["mlops.dev/on-success-trigger"])
if err != nil {
return fmt.Errorf("failed to parse success triggers: %w", err)
}
for _, trigger := range triggers {
switch trigger.Action {
case "restart":
// Delete pods to trigger reload
if err := r.restartResource(ctx, trigger); err != nil {
r.Recorder.Event(job, corev1.EventTypeWarning, "TriggerFailed",
fmt.Sprintf("Failed to restart %s/%s: %v", trigger.Kind, trigger.Name, err))
} else {
r.Recorder.Event(job, corev1.EventTypeNormal, "TriggerSuccess",
fmt.Sprintf("Restarted %s/%s", trigger.Kind, trigger.Name))
}
case "sync":
// Patch Application to trigger ArgoCD sync
if err := r.triggerArgoSync(ctx, trigger); err != nil {
r.Recorder.Event(job, corev1.EventTypeWarning, "TriggerFailed",
fmt.Sprintf("Failed to sync Application %s: %v", trigger.Name, err))
}
case "refresh":
// Update resource annotation to force refresh
if err := r.forceRefresh(ctx, trigger); err != nil {
r.Recorder.Event(job, corev1.EventTypeWarning, "TriggerFailed",
fmt.Sprintf("Failed to refresh %s/%s: %v", trigger.Kind, trigger.Name, err))
}
}
}
return nil
}
func (r *NotebookValidationJobReconciler) restartResource(ctx context.Context, trigger Trigger) error {
// For InferenceServices, delete predictor pods to force reload
if trigger.Kind == "InferenceService" {
labelSelector := fmt.Sprintf("serving.kserve.io/inferenceservice=%s", trigger.Name)
return r.deletePodsByLabel(ctx, trigger.Namespace, labelSelector)
}
// For other resources, add restart annotation
return r.addRestartAnnotation(ctx, trigger)
}
Supported Actions:
- restart: Delete pods to trigger reload (for KServe, deployments, etc.)
- sync: Trigger ArgoCD Application sync
- refresh: Add annotation to force resource refresh
Benefit: - Models auto-update after training - No manual intervention - Declarative dependency expression - Works across different resource types
Implementation Complexity: MEDIUM Estimated Effort: 2-3 weeks (controller logic, tests, documentation)
Feature 3: ArgoCD Sync Wave Awareness ⭐ MEDIUM PRIORITY¶
What: Operator respects and coordinates with ArgoCD sync waves
Why: Ensure notebooks complete before dependent resources deploy
Implementation: Add sync-wave blocking capabilities:
apiVersion: mlops.mlops.dev/v1alpha1
kind: NotebookValidationJob
metadata:
name: predictive-analytics-kserve-validation
annotations:
argocd.argoproj.io/sync-wave: "3"
mlops.dev/block-wave: "4" # Block wave 4 until this succeeds
spec:
# ...
Controller Logic: 1. When job phase = Succeeded, update annotation:
-
When job phase = Failed, update annotation:
-
ArgoCD sync hooks can check for
mlops.dev/wave-completebefore proceeding
Integration with ArgoCD: Users can create PreSync hooks that wait for wave completion:
apiVersion: batch/v1
kind: Job
metadata:
name: wait-for-notebook-wave3
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "4"
spec:
template:
spec:
containers:
- name: wait
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
until kubectl get notebookvalidationjob predictive-analytics-kserve-validation \
-o jsonpath='{.metadata.annotations.mlops\.dev/wave-complete}' | grep -q "3"; do
echo "Waiting for notebook validation wave 3 to complete..."
sleep 5
done
echo "Wave 3 complete, proceeding with wave 4"
restartPolicy: Never
Benefit: - Guaranteed ordering: notebooks → models → InferenceServices - Prevents race conditions - GitOps-native dependency management - Prevents deploying InferenceServices when models aren't ready
Implementation Complexity: LOW-MEDIUM (annotation management) Estimated Effort: 1-2 weeks
Feature 4: ArgoCD Application Status Integration ⭐ MEDIUM PRIORITY¶
What: Operator publishes aggregated status to ArgoCD Application annotations
Why: Centralized visibility of notebook validation state
Implementation: Controller updates parent Application with aggregated status:
# Controller updates parent Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: self-healing-platform
annotations:
mlops.dev/notebook-status: |
{
"total": 32,
"succeeded": 30,
"failed": 2,
"running": 0,
"lastUpdate": "2026-01-24T22:30:00Z",
"failedJobs": [
"predictive-analytics-kserve-validation",
"multi-cluster-healing-validation"
]
}
mlops.dev/notebook-summary: "30/32 succeeded (93.75% success rate)"
Controller Logic:
func (r *NotebookValidationJobReconciler) updateApplicationStatus(ctx context.Context, namespace string) error {
// List all NotebookValidationJobs in namespace
var jobs mlopsv1alpha1.NotebookValidationJobList
if err := r.List(ctx, &jobs, client.InNamespace(namespace)); err != nil {
return err
}
// Aggregate status
status := aggregateStatus(jobs.Items)
// Find parent ArgoCD Application (look for app.kubernetes.io/part-of label)
appName := findParentApplication(jobs.Items)
if appName == "" {
return nil // No parent Application found
}
// Update Application annotation
app := &unstructured.Unstructured{}
app.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
if err := r.Get(ctx, client.ObjectKey{Name: appName, Namespace: "argocd"}, app); err != nil {
return client.IgnoreNotFound(err)
}
annotations := app.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
statusJSON, _ := json.Marshal(status)
annotations["mlops.dev/notebook-status"] = string(statusJSON)
annotations["mlops.dev/notebook-summary"] = status.Summary()
app.SetAnnotations(annotations)
return r.Update(ctx, app)
}
Update Triggers: - After each job status change - Debounced to avoid excessive updates (max 1 update per minute) - Background reconciliation every 5 minutes
Benefit: - ArgoCD UI shows aggregated notebook status - No need to run separate kubectl commands - Better UX for operators - Single source of truth
Implementation Complexity: MEDIUM Estimated Effort: 2 weeks
Feature 5: ArgoCD Notification Hooks ⭐ LOW PRIORITY¶
What: Trigger ArgoCD notifications on job completion
Why: Alert operators when model training fails
Implementation: Operator creates Kubernetes Events that ArgoCD can consume:
# Controller creates Event when job fails
apiVersion: v1
kind: Event
metadata:
name: notebook-validation-failed-predictive-analytics
namespace: self-healing-platform
labels:
app.kubernetes.io/part-of: self-healing-platform
mlops.dev/notification-type: validation-failure
involvedObject:
apiVersion: mlops.mlops.dev/v1alpha1
kind: NotebookValidationJob
name: predictive-analytics-kserve-validation
namespace: self-healing-platform
type: Warning
reason: ValidationFailed
message: |
NotebookValidationJob 'predictive-analytics-kserve-validation' failed:
ModuleNotFoundError: No module named 'kserve_wrapper'
Retry count: 3/3 (exhausted)
Duration: 41 seconds
Phase: Failed
Suggested action: Check notebook cell execution for model serialization issues
Logs: kubectl logs predictive-analytics-kserve-validation-pod
ArgoCD Notification Integration: Users can configure ArgoCD notifications to trigger on these events:
# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
trigger.notebook-validation-failed: |
- when: app.status.health.status == 'Degraded'
oncePer: app.status.sync.revision
send: [notebook-failure-slack, notebook-failure-email]
template.notebook-failure-slack: |
message: |
:warning: Notebook Validation Failed in {{.app.metadata.name}}
Check failed notebook validations with:
`kubectl get notebookvalidationjob -n {{.app.spec.destination.namespace}}`
Benefit: - Proactive alerts - Faster incident response - Integration with existing ArgoCD notification channels (Slack, email, PagerDuty, etc.)
Implementation Complexity: LOW (uses existing Event API) Estimated Effort: 1 week
Implementation Roadmap¶
Phase 1: Foundation (Weeks 1-2) - COMPLETED ✅¶
Goal: Add basic ArgoCD awareness
Tasks:
1. ✅ Document ArgoCD health assessment configuration (Feature 1)
2. ✅ Create ADR-048
3. ✅ Add health check examples to operator documentation
4. ✅ Create config/argocd/health-check-configmap.yaml with Lua health check script
5. ✅ Create config/argocd/kustomization.yaml for Kustomize integration
6. ✅ Test health checks with existing NotebookValidationJobs
Deliverable: ArgoCD can display NotebookValidationJob health
Implementation Files:
- config/argocd/health-check-configmap.yaml - Lua health check for ArgoCD
- config/argocd/kustomization.yaml - Kustomize resource list
- docs/ARGOCD_INTEGRATION.md - User documentation
Phase 2: Resource Triggering (Weeks 3-5) - COMPLETED ✅¶
Goal: Implement post-success hooks (Feature 2)
Tasks:
1. ✅ Design annotation format for mlops.dev/on-success-trigger
2. ✅ Implement controller logic to parse and execute triggers
3. ✅ Add support for "restart", "sync", "refresh" actions
4. ✅ Add RBAC permissions for cross-resource management (kubebuilder markers)
5. ✅ Write unit tests for trigger parsing (trigger_handler_test.go)
6. ✅ Write integration tests (integration_test.go)
7. ✅ Document supported actions and examples
Deliverable: InferenceServices auto-restart after model training
Implementation Files:
- internal/controller/argocd/types.go - ResourceTrigger struct definition
- internal/controller/argocd/trigger_handler.go - Trigger execution logic
- internal/controller/argocd/trigger_handler_test.go - Unit tests
- internal/controller/argocd/integration_test.go - Integration tests
Supported Actions:
- restart - Delete pods to trigger reload (KServe InferenceService, Deployments)
- sync - Trigger ArgoCD Application sync
- refresh - Add annotation to force resource refresh
Phase 3: Sync Wave Integration (Weeks 6-7) - COMPLETED ✅¶
Goal: Coordinate with ArgoCD sync waves (Feature 3)
Tasks:
1. ✅ Implement sync-wave awareness in controller
2. ✅ Add wave-complete and wave-failed annotation on job completion
3. ✅ Add completion-time annotation with RFC3339 timestamp
4. ✅ Integrate sync wave logic into updateJobPhase() function
5. ✅ Write unit tests (sync_wave_test.go)
Deliverable: Notebooks block subsequent waves until completion
Implementation Files:
- internal/controller/argocd/sync_wave.go - Sync wave annotation logic
- internal/controller/argocd/sync_wave_test.go - Unit tests
Annotations Set on Completion:
- mlops.dev/wave-complete: "<wave-number>" - Set on success
- mlops.dev/wave-failed: "<wave-number>" - Set on failure
- mlops.dev/completion-time: "<RFC3339-timestamp>" - Always set
Phase 4: Observability (Weeks 8-10) - COMPLETED ✅¶
Goal: Improve visibility in ArgoCD UI (Features 4 & 5)
Tasks: 1. ✅ Implement Application status aggregation (Feature 4) 2. ✅ Add debounced update logic (1 minute debounce) 3. ✅ Implement notification event creation (Feature 5) 4. ✅ Create events with notification labels for ArgoCD integration 5. ✅ Integrate status aggregation into success/failure handlers
Deliverable: Full observability in ArgoCD UI with notifications
Implementation Files:
- internal/controller/argocd/status_aggregator.go - Application status aggregation
- internal/controller/argocd/events.go - Enhanced Kubernetes Events
Features:
- Aggregated notebook status in ArgoCD Application annotations
- mlops.dev/notebook-status - JSON status with total/succeeded/failed counts
- mlops.dev/notebook-summary - Human-readable summary string
- Enhanced Events with mlops.dev/notification-type label
- 1-minute debounce to prevent excessive updates
Example: End-to-End Workflow¶
Before (Current State)¶
# Wave 3: Notebook runs, trains model
apiVersion: mlops.mlops.dev/v1alpha1
kind: NotebookValidationJob
metadata:
name: predictive-analytics-kserve-validation
annotations:
argocd.argoproj.io/sync-wave: "3"
spec:
notebook:
git:
url: https://github.com/example/models
ref: main
path: notebooks/predictive-analytics.ipynb
# ... trains model to /mnt/models/predictive-analytics/model.pkl
---
# Wave 4: InferenceService deploys (races with notebook!)
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: predictive-analytics
annotations:
argocd.argoproj.io/sync-wave: "4"
spec:
predictor:
containers:
- name: kserve-container
volumeMounts:
- name: model-storage
mountPath: /mnt/models
Problems: - ❌ InferenceService may deploy before model exists - ❌ If model changes, InferenceService doesn't reload - ❌ No visibility in ArgoCD about notebook status - ❌ Manual pod deletion required after model training
After (With ArgoCD Integration)¶
# Wave 3: Notebook runs, trains model
apiVersion: mlops.mlops.dev/v1alpha1
kind: NotebookValidationJob
metadata:
name: predictive-analytics-kserve-validation
annotations:
argocd.argoproj.io/sync-wave: "3"
mlops.dev/block-wave: "4" # ← Feature 3: Block wave 4 until success
mlops.dev/on-success-trigger: | # ← Feature 2: Auto-restart InferenceService
- apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
name: predictive-analytics
namespace: self-healing-platform
action: restart
spec:
notebook:
git:
url: https://github.com/example/models
ref: main
path: notebooks/predictive-analytics.ipynb
# ... trains model to /mnt/models/predictive-analytics/model.pkl
# Operator automatically sets when job succeeds:
# mlops.dev/wave-complete: "3"
# mlops.dev/completion-time: "2026-01-24T22:30:00Z"
---
# Wave 4: Wait for notebook completion
apiVersion: batch/v1
kind: Job
metadata:
name: wait-for-models
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "4"
spec:
template:
spec:
containers:
- name: wait
image: bitnami/kubectl:latest
command: ["/bin/bash", "-c"]
args:
- |
until kubectl get notebookvalidationjob predictive-analytics-kserve-validation \
-o jsonpath='{.metadata.annotations.mlops\.dev/wave-complete}' | grep -q "3"; do
echo "Waiting for notebook validation to complete..."
sleep 5
done
restartPolicy: Never
---
# Wave 4: InferenceService deploys ONLY AFTER notebook succeeds
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: predictive-analytics
annotations:
argocd.argoproj.io/sync-wave: "4"
spec:
predictor:
containers:
- name: kserve-container
volumeMounts:
- name: model-storage
mountPath: /mnt/models
Benefits: - ✅ InferenceService waits for model to exist (Feature 3) - ✅ InferenceService auto-restarts when model updates (Feature 2) - ✅ ArgoCD UI shows notebook validation status (Feature 1) - ✅ Application health includes notebook state (Feature 4) - ✅ Operators get notified if training fails (Feature 5) - ✅ No manual intervention required - ✅ Fully declarative GitOps workflow
Consequences¶
Positive¶
- Complete GitOps Workflow: Notebooks become first-class citizens in ArgoCD
- Automatic Model Updates: No manual intervention for model reloading
- Better Observability: Single pane of glass in ArgoCD UI
- Race Condition Prevention: Sync waves guarantee ordering
- Production Ready: Aligns with enterprise GitOps practices
- Platform Agnostic: Works across OpenShift, vanilla Kubernetes with ArgoCD
- Incremental Adoption: Features can be adopted independently
Negative¶
- Increased Complexity: More annotation-based configuration
- ArgoCD Dependency: Features only work with ArgoCD (not Flux, etc.)
- RBAC Requirements: Controller needs permissions to update Applications, delete pods
- Testing Burden: Need e2e tests with ArgoCD environment
- Documentation Overhead: Multiple integration patterns to document
Neutral¶
- Backward Compatible: All features are opt-in via annotations
- Feature Flags: Can gate features behind configuration
- Migration Path: Existing deployments unaffected
Related ADRs¶
- ADR-007: Distribution and Catalog Strategy - Mentions ArgoCD integration as strategic goal
- ADR-010: Observability and Monitoring Strategy - Status reporting foundation
- ADR-011: Error Handling and Retry Strategy - Failure notifications build on retry logic
- ADR-042: ArgoCD Deployment Lessons Learned (openshift-aiops-platform) - Real-world issues driving this
References¶
- ArgoCD Health Assessment: https://argo-cd.readthedocs.io/en/stable/operator-manual/health/
- ArgoCD Sync Waves: https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/
- ArgoCD Notifications: https://argocd-notifications.readthedocs.io/
- KServe InferenceService: https://kserve.github.io/website/latest/
- NotebookValidationJob CRD:
api/v1alpha1/notebookvalidationjob_types.go
Implementation Notes¶
RBAC Additions Required¶
For Feature 2 (Resource Triggering), the operator ClusterRole needs:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: jupyter-notebook-validator-operator-manager
rules:
# Existing rules...
# Feature 2: Resource triggering
- apiGroups: ["serving.kserve.io"]
resources: ["inferenceservices"]
verbs: ["get", "list", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "delete"]
# Feature 4: Application status updates
- apiGroups: ["argoproj.io"]
resources: ["applications"]
verbs: ["get", "list", "patch"]
Metrics to Add¶
For Prometheus/ArgoCD integration:
var (
notebookValidationDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "notebook_validation_duration_seconds",
Help: "Duration of notebook validation jobs",
},
[]string{"job_name", "phase"},
)
notebookValidationTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "notebook_validation_total",
Help: "Total number of notebook validations",
},
[]string{"phase"},
)
argocdTriggersTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_triggers_total",
Help: "Total number of ArgoCD-related triggers",
},
[]string{"action", "status"},
)
)
Implementation Summary¶
All phases have been completed as of 2026-01-24:
- ✅ Create ADR-048 (this document)
- ✅ Create GitHub issue with feature request (#5)
- ✅ Phase 1: Foundation - Health assessment configuration and documentation
- ✅ Phase 2: Resource Triggering - Post-success hooks with restart/sync/refresh actions
- ✅ Phase 3: Sync Wave Integration - Wave-complete/wave-failed annotations
- ✅ Phase 4: Observability - Status aggregation and notification events
Files Added¶
config/argocd/
├── health-check-configmap.yaml # ArgoCD Lua health check
└── kustomization.yaml # Kustomize integration
internal/controller/argocd/
├── events.go # Enhanced notification events
├── integration_test.go # Integration tests
├── status_aggregator.go # Application status aggregation
├── sync_wave.go # Sync wave annotation logic
├── sync_wave_test.go # Sync wave tests
├── trigger_handler.go # Resource trigger execution
├── trigger_handler_test.go # Trigger handler tests
└── types.go # ResourceTrigger struct
RBAC Additions (via kubebuilder markers)¶
//+kubebuilder:rbac:groups=serving.kserve.io,resources=inferenceservices,verbs=get;list;watch;patch
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;patch
//+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;patch;update
Success Criteria¶
After implementing these features:
✅ Visibility: - ArgoCD Application health reflects notebook validation status - Operators can see which notebooks are running/failed in ArgoCD UI - No need to run kubectl commands separately
✅ Automation: - InferenceServices automatically restart when new models are trained - No manual intervention required - Models stay current without drift
✅ Reliability: - Notebooks complete before InferenceServices deploy - No race conditions - Sync waves guarantee ordering
✅ Observability: - Failed notebook validations trigger alerts - Application status shows aggregated notebook health - Single pane of glass for GitOps + ML workflows