CGOA — GitOps Certified Associate
Create Drift, See It, and Undo It
Goal
You create drift yourself, compute the difference and leave it in a file, and then go through one full lap by hand, reverting to the declared state. Next you write sync wave and PreSync hook manifests, and search the cluster to determine the prune targets.
Why it matters
Argo CD's reconcile loop is not magic; it repeats what you do in this lab every 180 seconds by default. It re-renders, reads live, compares, and applies if the policy allows. If you run this loop by hand once, you come to understand in your bones why refresh and sync are different actions and what discipline turning on selfHeal demands of operations. The same goes for determining prune targets — the agent does not delete every resource in the cluster; it deletes only those, among the ones it has marked as created by itself, that have disappeared from Git. If you do not know this boundary, you cannot explain either "why isn't that one being deleted?" or "why did that get deleted?" The Argo CD CRDs are not in this environment, so you write waves and hooks as files, and grading also works by reading the files.
Steps
- In
/root/cgoa-drift/desired/orders.yaml, write a Deployment namedorders— namespacecgoa-drift,spec.replicas: 2, selector and Pod labelsapp: orders, container nameapp, imagenginx:1.27-alpine. Create the namespacecgoa-driftand apply this file withkubectl apply -f. - Leaving the file as is, change only the
ordersreplicas on the cluster to5to create drift. Then read the changed live value withkubectl get ... -o jsonpathand save it to/root/cgoa-drift/live-replicas.txt(it stays as evidence even after you revert it later). - Save the output of
kubectl diff -f /root/cgoa-drift/desired/orders.yamlto/root/cgoa-drift/drift.txt. The file must show the replicas difference. - Apply the original manifest again to revert replicas to
2. - In
/root/cgoa-drift/wave/, create three files.namespace.yaml— Namespacecgoa-wave, annotationargocd.argoproj.io/sync-wave: "-2".config.yaml— ConfigMaporders-wave-config(namespacecgoa-wave, any one key in data), annotationargocd.argoproj.io/sync-wave: "-1".app.yaml— Deploymentorders-wave(namespacecgoa-wave), annotationargocd.argoproj.io/sync-wave: "1". - In
/root/cgoa-drift/hooks/db-migrate.yaml, write a Job nameddb-migrate— namespacecgoa-drift, the annotationsargocd.argoproj.io/hook: PreSync,argocd.argoproj.io/hook-delete-policy: BeforeHookCreation, andargocd.argoproj.io/sync-wave: "-1",spec.backoffLimit: 1, the Pod'srestartPolicy: Never, and container namemigrate. - Determine the prune targets. First, declare a ConfigMap
orders-config(namespacecgoa-drift, any one key in data) in/root/cgoa-drift/desired/config.yamland apply it. Then create a ConfigMap namedlegacy-configin thecgoa-driftnamespace imperatively, without a file. Finally, in/root/cgoa-drift/prune-candidates.txt, write, one per line, the names of the ConfigMaps in thecgoa-driftnamespace that are not declared anywhere in thedesired/directory. (kube-root-ca.crt, which Kubernetes creates automatically, is not a managed object, so exclude it.)
Notes
kubectl diff -f <파일> > out.txt(the placeholder is the file) has exit code 1 when there are differences. Append|| true.- Wave numbers are always strings wrapped in quotes. If you write
sync-wave: -2, YAML reads it as a number and the annotation value type does not match. - The two hook annotation keys are different:
argocd.argoproj.io/hookandargocd.argoproj.io/hook-delete-policy. - Common mistake: saving the result of
kubectl get -o yamlin step 3. That is only the live state, not a difference. Be sure to save the diff.
Declare and apply the desired state
In /root/cgoa-drift/desired/orders.yaml, write a Deployment named orders — namespace cgoa-drift, spec.replicas: 2, selector and Pod labels app: orders, container name app, image nginx:1.27-alpine. Create the namespace cgoa-drift and apply this file with kubectl apply -f.
When you apply declaratively, the last applied configuration is left on the object as an annotation. If you create it imperatively, that annotation is absent — grading looks at that difference.
Create drift by editing by hand
Leaving the file as is, change only the orders replicas on the cluster to 5 to create drift. Then read the changed live value with kubectl get ... -o jsonpath and save it to /root/cgoa-drift/live-replicas.txt (it stays as evidence even after you revert it later).
You must leave the file as is and change only the cluster. There is an imperative subcommand that changes the scale. Extract the changed live value with jsonpath and leave it in a file — it serves as evidence even after you revert.
Compute the difference and leave it in a file
Save the output of kubectl diff -f /root/cgoa-drift/desired/orders.yaml to /root/cgoa-drift/drift.txt. The file must show the replicas difference.
kubectl diff -f <파일> (the placeholder is the file) sends a dry run to the server and shows the actual difference. When there are differences the exit code is 1, so make sure it does not abort when you redirect.
Revert to the declared state
Apply the original manifest again to revert replicas to 2.
You do by hand what the agent's selfHeal does. Just apply the original file again as is.
Three sync wave annotations
In /root/cgoa-drift/wave/, create three files. namespace.yaml — Namespace cgoa-wave, annotation argocd.argoproj.io/sync-wave: "-2". config.yaml — ConfigMap orders-wave-config (namespace cgoa-wave, any one key in data), annotation argocd.argoproj.io/sync-wave: "-1". app.yaml — Deployment orders-wave (namespace cgoa-wave), annotation argocd.argoproj.io/sync-wave: "1".
Write wave numbers as strings. Choose the numbers so that infrastructure comes first, configuration next, and the workload last.
PreSync hook Job
In /root/cgoa-drift/hooks/db-migrate.yaml, write a Job named db-migrate — namespace cgoa-drift, the annotations argocd.argoproj.io/hook: PreSync, argocd.argoproj.io/hook-delete-policy: BeforeHookCreation, and argocd.argoproj.io/sync-wave: "-1", spec.backoffLimit: 1, the Pod's restartPolicy: Never, and container name migrate.
The hook type and the deletion policy are different annotation keys. For the deletion policy, it is better for review to write the default value explicitly.
Determine the prune targets
Determine the prune targets. First, declare a ConfigMap orders-config (namespace cgoa-drift, any one key in data) in /root/cgoa-drift/desired/config.yaml and apply it. Then create a ConfigMap named legacy-config in the cgoa-drift namespace imperatively, without a file. Finally, in /root/cgoa-drift/prune-candidates.txt, write, one per line, the names of the ConfigMaps in the cgoa-drift namespace that are not declared anywhere in the desired/ directory. (kube-root-ca.crt, which Kubernetes creates automatically, is not a managed object, so exclude it.)
A prune target is a resource that is "in the cluster but not in the declaration directory." Remember that objects the cluster creates automatically are not targets.