9ebc9c8868
---ci--- project: atelier phase: 6 milestone: v0.3 status: complete requirements: covered: [ATELIER-60..91] partial: [] ---/ci---
197 lines
7.4 KiB
Markdown
197 lines
7.4 KiB
Markdown
# 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
|
|
|
|
```yaml
|
|
# 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
|
|
```
|
|
|
|
```yaml
|
|
# 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"]
|
|
```
|
|
|
|
```yaml
|
|
# 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)
|
|
|
|
```text
|
|
# .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 revert` is the rollback — the recovery path is the
|
|
history.
|
|
- See `domains/gitops-operators/first-principles.md` P1 and
|
|
`domains/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 no
|
|
`kubectl` rights 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) and
|
|
`domains/gitops-operators/flux.md` for 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, not `latest`. The commit that changed it is a permanent
|
|
record; `git revert` restores `1.2.2` and ArgoCD's `selfHeal`
|
|
converges the cluster back. No force-push; history is the audit
|
|
trail.
|
|
- See `domains/gitops-operators/first-principles.md` P5 and
|
|
`domains/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 in `PreSync` before 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: true` means a hand-edited drift on a
|
|
managed resource is overwritten on the next loop. The fix for drift
|
|
is a new commit, not `kubectl edit`. The PR author does not SSH into
|
|
the cluster to "fix" anything.
|
|
- See `domains/gitops-operators/argocd.md` (Diff and Drift) and
|
|
`domains/gitops-operators/first-principles.md` P8.
|
|
|
|
### Least Privilege Reconciliation (GitOps P10, C8 Economy)
|
|
- The AppProject `payments` restricts the Application to the
|
|
`payments` namespace and the `payments` repo. The controller's
|
|
ServiceAccount (not shown) is bound to a namespace-scoped Role, not
|
|
`cluster-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) and
|
|
`domains/kubernetes/rbac.md`.
|
|
|
|
### Policy is a Gate (Compliance P5, cross-link)
|
|
- The `opa eval` step 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.md` and
|
|
`domains/devops/ci-cd.md`.
|
|
|
|
### Failure is Observable (GitOps P9)
|
|
- A sync failure or health degradation on `payments-api` emits
|
|
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) and
|
|
`domains/observability/metrics.md`.
|
|
|
|
## What This PR Does NOT Do (And Why That's Good)
|
|
|
|
- Does **not** run `kubectl apply` from CI — that is the push pattern,
|
|
a P3 violation (see `examples/bad/` for the anti-pattern).
|
|
- Does **not** use `argocd app set` as 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 revert` is the rollback;
|
|
`selfHeal` is the convergence. |