4c8700e5a4
---ci--- project: atelier phase: 5 milestone: v0.3 status: complete requirements: covered: [ATELIER-86, ATELIER-87, ATELIER-88] partial: [] ---/ci---
7.4 KiB
7.4 KiB
Good Example: GitOps Pull Request
A pull request that changes ArgoCD Application manifests following Atelier's GitOps + Operators principles. Each aspect cites the principle it satisfies.
The PR
A PR titled promote payments-api 1.2.3 to prod opened against the
GitOps repo platform/gitops. It changes the targetRevision of the
payments-api Application from 1.2.2 to 1.2.3, adds a sync-wave
annotation to a new migration Job, and tightens the AppProject's
destination allow-list. CI runs plan/diff; nothing pushes to the
cluster.
The Commit
# manifests/prod/payments-api.yaml — the only file changed
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-api
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
source:
repoURL: https://git.example.com/platform/payments
targetRevision: 1.2.3 # was 1.2.2 — pinned, not latest
path: manifests/prod
destination:
server: https://kubernetes.default.svc
namespace: payments
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=false
- PrunePropagationPolicy=foreground
# manifests/prod/payments-db-migration.yaml — new file, wave-ordered
apiVersion: batch/v1
kind: Job
metadata:
name: payments-db-migrate-1.2.3
namespace: payments
annotations:
argocd.argoproj.io/sync-wave: "-1" # PreSync: run before the app
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
serviceAccountName: payments-migrator
containers:
- name: migrate
image: registry.example.com/payments/migrate:1.2.3
args: ["up", "--to", "1.2.3"]
# projects/payments.yaml — AppProject tightened in the same PR
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: payments
namespace: argocd
spec:
sourceRepos:
- https://git.example.com/platform/payments
destinations:
- server: https://kubernetes.default.svc
namespace: payments # prod-app can only deploy to payments ns
clusterResourceWhitelist:
- group: ""
kind: Namespace # allowed to create its own namespace
roles:
- name: payments-team
policies:
- p, proj:payments:payments-team, applications, sync, payments/*, allow
The CI Pipeline (runs on the PR, before merge)
# .github/workflows/gitops-plan.yml (illustrative steps)
- name: validate manifests
run: argocd app manifests manifests/prod/ | kubeconform -strict
- name: diff against live cluster (read-only, no apply)
run: argocd app diff payments-api --server $ARGOCD_SERVER --auth-token $READ_ONLY_TOKEN
# CI holds a READ-ONLY ArgoCD token. It never holds kubectl rights.
# A non-empty diff is the PR's proposed change, rendered for review.
- name: opa gate (admission policy pre-check)
run: opa eval -i manifests/prod/ -d policies/ "data.k8s.admission.deny"
# Policy violations fail the PR before merge, not after deploy.
What Makes It Good
Git is the Source of Truth (GitOps P1, C1 Correctness)
- The promotion is a commit. The cluster's desired state is a
derivative of this repo; the repo is the authority. If the change is
wrong,
git revertis the rollback — the recovery path is the history. - See
domains/gitops-operators/first-principles.mdP1 anddomains/gitops-operators/argocd.md(Application CRD).
Pull, Don't Push (GitOps P3, C4 Locality)
- CI holds a read-only ArgoCD token for
app diff. It holds nokubectlrights against the production cluster. The cluster's ArgoCD controller pulls the merged commit; nothing pushes to the cluster. A compromised CI token can read, not deploy. - See
domains/gitops-operators/argocd.md(RBAC and SSO) anddomains/gitops-operators/flux.mdfor the same pull boundary from the Flux side.
State is Immutable and Versioned (GitOps P5, C5 Reversibility)
targetRevision: 1.2.3— the Application pins a specific chart revision, notlatest. The commit that changed it is a permanent record;git revertrestores1.2.2and ArgoCD'sselfHealconverges the cluster back. No force-push; history is the audit trail.- See
domains/gitops-operators/first-principles.mdP5 anddomains/infrastructure-as-code/state.md(State is Truth).
Sync Waves Order Correctness (GitOps P4, C1)
- The migration Job carries
argocd.argoproj.io/sync-wave: "-1"so it runs inPreSyncbefore the payments-api Deployment that depends on the new schema. Wave ordering is a correctness mechanism, not performance — the app starting before its migration is a correctness bug. - See
domains/gitops-operators/argocd.md(Sync Waves and Hooks).
Reconcile, Don't Mutate by Hand (GitOps P8)
selfHeal: true+prune: truemeans a hand-edited drift on a managed resource is overwritten on the next loop. The fix for drift is a new commit, notkubectl edit. The PR author does not SSH into the cluster to "fix" anything.- See
domains/gitops-operators/argocd.md(Diff and Drift) anddomains/gitops-operators/first-principles.mdP8.
Least Privilege Reconciliation (GitOps P10, C8 Economy)
- The AppProject
paymentsrestricts the Application to thepaymentsnamespace and thepaymentsrepo. The controller's ServiceAccount (not shown) is bound to a namespace-scoped Role, notcluster-admin. The PR tightens the allow-list — least privilege is a direction, not a one-time setting. - See
domains/gitops-operators/argocd.md(RBAC and SSO) anddomains/kubernetes/rbac.md.
Policy is a Gate (Compliance P5, cross-link)
- The
opa evalstep runs the admission policy against the proposed manifests before merge. A violation fails the PR; the non-compliant state is never realized. Detection is not enforcement; this is enforcement. - See
domains/compliance/policy-as-code.mdanddomains/devops/ci-cd.md.
Failure is Observable (GitOps P9)
- A sync failure or health degradation on
payments-apiemits ArgoCD status (Degraded/OutOfSync) and a notification. Silent drift is the bug; this PR does not disable notifications. - See
domains/gitops-operators/argocd.md(Health and Status) anddomains/observability/metrics.md.
What This PR Does NOT Do (And Why That's Good)
- Does not run
kubectl applyfrom CI — that is the push pattern, a P3 violation (seeexamples/bad/for the anti-pattern). - Does not use
argocd app setas the steady state — the change is in git, not in an imperative command's history. - Does not store raw Secrets in the GitOps repo — secrets arrive via Sealed Secrets / SOPS / External Secrets, encrypted in git.
- Does not float
targetRevision: latest— the Application pins a version; "latest" is an unknown model of the system.
Cross-Domain Links
domains/gitops-operators/argocd.md— the Application CRD, sync waves, RBAC/AppProjects, and the pull model.domains/gitops-operators/flux.md— the same PR pattern from the Flux side (Kustomization CRD, per-cluster autonomy).domains/kubernetes/workloads.md— the Deployment/Job the Application reconciles.domains/kubernetes/rbac.md— the ServiceAccount + Role the controller and the migration Job run as.domains/compliance/policy-as-code.md— the OPA gate is a compliance-as-a-gate enforcement point.domains/devops/P4 Rollback First—git revertis the rollback;selfHealis the convergence.