MLOps Platform Engineering

Push vs Pull: Two CI/CD Philosophies for AI Services

The architectural difference between push-based "reactor" pipelines for Cloud Run and pull-based GitOps sync for GKE — and how Jenkins orchestrates both patterns.

10 min read
The Gatekeeper: Build Pipelines

Build pipelines — whether targeting GKE or Cloud Run — act as strict gatekeepers. A Git tag push (e.g., 1.2.3-uat.1) triggers a webhook to Jenkins. The GenericTrigger plugin uses a regex filter to validate the tag format and extract the environment suffix before the pipeline even starts. A UAT pipeline only activates for -uat tags; a -dev tag is silently rejected. Think of it as a bouncer checking your ID at the door. This protects upstream environments from unverified, ad-hoc changes.

groovy
// Jenkins Generic Webhook Trigger — the "bouncer" pattern
// The Expression regex validates tag format AND environment match.
// Text is assembled from webhook payload variables:
//   $appEnvironment = extracted env suffix (e.g. "uat")
//   $changesType    = webhook action type ("ADD" or "UPDATE")
//   $ref            = full git ref ("refs/tags/1.2.3-uat.1")
//
// Match:  "uat-ADD-refs/tags/1.2.3-uat.1" → pipeline runs
// Reject: "dev-ADD-refs/tags/1.2.3-dev.1" → UAT pipeline blocked
regexpFilterExpression: "^uat-(ADD|UPDATE)-refs/tags/\\d+\\.\\d+\\.\\d(-uat)?(\\.\\d+)?$"
regexpFilterText: "$appEnvironment-$changesType-$ref" 
The Reactor: Cloud Run Deploy Pipelines

Cloud Run deploy pipelines follow the opposite philosophy. When a PR is merged into the config repo, the pipeline accepts the event unconditionally — like a mailroom accepting all packages. The intelligence lives inside the pipeline stages: it runs git diff to determine which service YAML files changed, then deploys only those services. A single merge can update multiple services simultaneously; unchanged services are never touched. This provides maximum speed and efficiency.

ArgoCD: The Pull Model for GKE

For GKE, no deploy webhook is needed at all. The build pipeline creates a PR to the GKE config repository with the updated image tag. Once merged, ArgoCD detects the drift automatically and syncs the manifests to the cluster. With selfHeal: true, even manual kubectl changes are automatically reverted. The entire deployment state is always exactly the state of Git. This pull-based model is incredibly resilient and self-healing.

yaml
# GKE Config Repo values.yaml — Jenkins only updates the tag
image:
  repository: asia-southeast2-docker.pkg.dev/my-project/my-repo/my-service
  tag: "1.2.3-dev.1"  # ← CI updates this single line via PR

# ArgoCD auto-syncs on commit — no webhook needed
syncPolicy:
  automated:
    prune: true
    selfHeal: true
Push vs Pull Deployment
flowchart LR Jenkins-- "Push: gcloud run replace" -->CloudRun[Cloud Run] Jenkins-- "Commit: Update image tag" -->GitRepo[Config Repo] ArgoCD-- "Pull: Detect drift" -->GitRepo ArgoCD-- "Apply manifests" -->GKE[GKE Cluster]
Lessons Learned: Git Branching Strategy

A major gotcha is coordinating branching strategies between the two models. In our push-based Cloud Run setup, developers were tempted to deploy directly from feature branches, bypassing UAT governance. In our pull-based GKE cluster, direct commits to the main config repo caused sync conflicts when multiple automated PRs landed at once. The lesson: establish strict trunk-based development with auto-squashing PRs, and enforce repository branch protections so that only certified pipelines can write to the environment directories.

More Recent Posts