Writing Application and AppProject Yourself
Goal
You write ArgoCD's Application and AppProject yourself and become able to express as a declaration "what to read from where and apply to where, in what order and under what policy".
Why it matters
If you make a deployment an object rather than a script, three things follow. You can ask the cluster "what is it being deployed right now", RBAC and audit logs attach for free, and you can commit that declaration itself to git again. Every field you fill in this lab has a matching real incident — prune is a switch that can turn a single typo in a repository path into a mass deletion, without retry.backoff a failed synchronization becomes a load generator knocking on the API server at the same interval, and without ignoreDifferences an infinite loop arises in which the HPA and ArgoCD keep reverting replicas on each other. However, the ArgoCD controller does not run in this environment. You can register the CRDs and create custom resources, but they do not change to Synced/Healthy by themselves. So what is graded is not the state but the accuracy of the declaration, and what that declaration makes a real controller do is covered in the reading material before it and in the last module.
Steps
- From the offline CRD bundle under
/opt/crds/, find the file that holds the ArgoCD CRDs (grep -l applications.argoproj.io /opt/crds/*.yaml) and apply it withkubectl apply -f. The two CRDsapplications.argoproj.ioandappprojects.argoproj.iomust be created with theEstablishedconditionTrue, and the namespaceargocdmust exist. Save the list of registered types to/root/gitops/app/out/crds.txt(it must contain the stringapplications). - In the
argocdnamespace, create an object withkind: Applicationandmetadata.name: web.spec.source.repoURLisfile:///root/gitops/repo,spec.source.pathisapps/web,spec.source.targetRevisionismain,spec.destination.serverishttps://kubernetes.default.svc,spec.destination.namespaceisgitops-lab, andspec.projectisplatform. - To
web, addspec.syncPolicy.automatedand setprune: trueandselfHeal: true. And write two or three lines in Korean in/root/gitops/app/out/prune-note.txtabout the risk of turning on prune (a single commit that points at the wrong path leads to a mass deletion). - In
web'sspec.syncPolicy.syncOptions, putCreateNamespace=trueandServerSideApply=true, and inspec.syncPolicy.retry, putlimit: 3,backoff.duration: 10s,backoff.factor: 2, andbackoff.maxDuration: 5m. - If the repository
/root/gitops/repodoes not exist yet, create it first — copy, from/opt/lab/fixtures/gitops/seed/,deployment.yamlandservice.yamlto/root/gitops/repo/apps/web/, thengit initand commit. Then, in the repository's/root/gitops/repo/apps/web/, add toservice.yamlthe annotationargocd.argoproj.io/sync-wave: "-1"and todeployment.yamlthe annotationargocd.argoproj.io/sync-wave: "0". The value must be a string wrapped in double quotes. And write in/root/gitops/app/out/wave-note.txtthat within the same wave, resources are applied in the default order by resource kind. - In
/root/gitops/repo/apps/web/presync-job.yaml, create a hook resource withkind: Job. Put inargocd.argoproj.io/hook: PreSyncandargocd.argoproj.io/hook-delete-policy: BeforeHookCreationas annotations, name the containermigrate, setspec.backoffLimitto1, and set the Pod'srestartPolicytoNever. In this directory, the file with a hook annotation must be this one only. - In the
argocdnamespace, createkind: AppProjectwithmetadata.name: platform. Inspec.sourceRepos, put onlyfile:///root/gitops/repo(*forbidden); inspec.destinations[0], put serverhttps://kubernetes.default.svcand namespacegitops-lab(*forbidden); inspec.clusterResourceWhitelist, put group""/ kindNamespace; and inspec.namespaceResourceBlacklist, put group""/ kindResourceQuotaand group""/ kindLimitRange. For Applicationweb,spec.projectmust beplatform. - To
web, addspec.ignoreDifferences— groupapps, kindDeployment, and injsonPointers,/spec/replicas(a field owned by the HPA). And setspec.revisionHistoryLimitto5. Finally, create/root/gitops/app/out/gitops-report.json.applicationsis an array that holds all Applications of theargocdnamespace in the form{"name": "..."},projectis"platform", andself_healistrue.
Notes
- The repository an
Applicationpoints to is a local path inside this Pod. If you did the previous lab in another Pod,/root/gitops/repois empty, so in step 5 you must recreate it from the fixtures (/opt/lab/fixtures/gitops/seed/). Checking that what a declaration points to actually exists is also the job of GitOps. - This environment has no remote git, so
repoURLis a local path (file://). In practice anhttps://orgit@address comes here, and that repository's credentials are managed as a Secret in theargocdnamespace (with theargocd.argoproj.io/secret-type: repositorylabel). - You can create manifests with
kubectl apply -f 파일(the placeholder stands for the file). The CRDs must be registered first for theApplicationtype to be recognized. - Make the report in step 8 not by matching the count by hand but from the result of asking the cluster. If you process
kubectl get application -n argocd -o jsonwithjq, the count matches automatically even as apps increase. - Common mistake 1: writing the sync wave value without quotes, like
argocd.argoproj.io/sync-wave: -1. An annotation value must be a string, and without quotes the YAML parser reads it as a number and the apply itself is rejected. - Common mistake 2: thinking the hook annotation key is one.
argocd.argoproj.io/hookandargocd.argoproj.io/hook-delete-policyare different keys and both are needed.
Register the ArgoCD API types
From the offline CRD bundle under /opt/crds/, find the file that holds the ArgoCD CRDs (grep -l applications.argoproj.io /opt/crds/*.yaml) and apply it with kubectl apply -f. The two CRDs applications.argoproj.io and appprojects.argoproj.io must be created with the Established condition True, and the namespace argocd must exist. Save the list of registered types to /root/gitops/app/out/crds.txt (it must contain the string applications).
There is no internet, so you use the offline CRD bundle. Instead of memorizing the file name, find it by content — you can find with grep which file contains applications.argoproj.io.
Define the Application's source and destination
In the argocd namespace, create an object with kind: Application and metadata.name: web. spec.source.repoURL is file:///root/gitops/repo, spec.source.path is apps/web, spec.source.targetRevision is main, spec.destination.server is https://kubernetes.default.svc, spec.destination.namespace is gitops-lab, and spec.project is platform.
The place where the Application object itself lives and the namespace it deploys to are different. In source, you need all three: where to read from, which path, and which revision.
Turn on automatic synchronization, pruning, and self-healing
To web, add spec.syncPolicy.automated and set prune: true and selfHeal: true. And write two or three lines in Korean in /root/gitops/app/out/prune-note.txt about the risk of turning on prune (a single commit that points at the wrong path leads to a mass deletion).
The two switches under automated do different jobs. One handles what was deleted from the repository, and the other handles what changed in the cluster. You must also write which one is the dangerous side.
Put in sync options and retry backoff
In web's spec.syncPolicy.syncOptions, put CreateNamespace=true and ServerSideApply=true, and in spec.syncPolicy.retry, put limit: 3, backoff.duration: 10s, backoff.factor: 2, and backoff.maxDuration: 5m.
syncOptions is an array of 키=값 strings (key=value). For retry, a count alone is not enough; you need three values that make the interval grow wider.
Build a deployment order with sync waves
If the repository /root/gitops/repo does not exist yet, create it first — copy, from /opt/lab/fixtures/gitops/seed/, deployment.yaml and service.yaml to /root/gitops/repo/apps/web/, then git init and commit. Then, in the repository's /root/gitops/repo/apps/web/, add to service.yaml the annotation argocd.argoproj.io/sync-wave: "-1" and to deployment.yaml the annotation argocd.argoproj.io/sync-wave: "0". The value must be a string wrapped in double quotes. And write in /root/gitops/app/out/wave-note.txt that within the same wave, resources are applied in the default order by resource kind.
The wave value is an annotation and is written not as a number but as a string wrapped in double quotes. Give a smaller value, and a negative one if needed, to what must be created first. To make an order, there must be two or more files.
Write a PreSync hook Job
In /root/gitops/repo/apps/web/presync-job.yaml, create a hook resource with kind: Job. Put in argocd.argoproj.io/hook: PreSync and argocd.argoproj.io/hook-delete-policy: BeforeHookCreation as annotations, name the container migrate, set spec.backoffLimit to 1, and set the Pod's restartPolicy to Never. In this directory, the file with a hook annotation must be this one only.
A hook is usually a Job. Two annotations are needed, one deciding which phase and the other when to clean up. If you leave out the cleanup policy, hook resources keep piling up.
Draw a boundary with an AppProject
In the argocd namespace, create kind: AppProject with metadata.name: platform. In spec.sourceRepos, put only file:///root/gitops/repo (* forbidden); in spec.destinations[0], put server https://kubernetes.default.svc and namespace gitops-lab (* forbidden); in spec.clusterResourceWhitelist, put group "" / kind Namespace; and in spec.namespaceResourceBlacklist, put group "" / kind ResourceQuota and group "" / kind LimitRange. For Application web, spec.project must be platform.
A whitelist is "allow only what is written", and a blacklist is "forbid only what is written". If you use * for repositories and namespaces, there is no point in splitting the project. Do not forget to make the app belong to that project.
Specify fields to ignore and build the configuration report
To web, add spec.ignoreDifferences — group apps, kind Deployment, and in jsonPointers, /spec/replicas (a field owned by the HPA). And set spec.revisionHistoryLimit to 5. Finally, create /root/gitops/app/out/gitops-report.json. applications is an array that holds all Applications of the argocd namespace in the form {"name": "..."}, project is "platform", and self_heal is true.
If you also revert fields owned by another controller, it becomes infinite synchronization. Make the number of apps in the report not by counting by hand but from the result of asking the cluster — then it always matches reality.