diff --git a/README.md b/README.md index e79854ba..53cbcff8 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ make docs-serve ## Examples - [`examples/cloudnativepg/`](examples/cloudnativepg/) — Deploy a `CoderControlPlane` with a CloudNativePG-managed PostgreSQL backend. +- [`examples/argocd/`](examples/argocd/) — Bootstrap CloudNativePG + `coder-k8s` + PostgreSQL + `CoderControlPlane` from one Argo CD `ApplicationSet`. ## KIND development cluster (k9s demos) diff --git a/examples/argocd/README.md b/examples/argocd/README.md new file mode 100644 index 00000000..c1533d3b --- /dev/null +++ b/examples/argocd/README.md @@ -0,0 +1,70 @@ +# Argo CD single-apply example (CloudNativePG + coder-k8s) + +This example bootstraps the full stack from one `ApplicationSet`. + +The generated Argo CD `Application` uses multiple sources to deploy: + +1. CloudNativePG operator (Helm chart) +2. `coder-k8s` operator stack (`config/crd/bases`, `config/rbac`, `deploy`) +3. CloudNativePG PostgreSQL `Cluster` +4. `CoderControlPlane` + +## Prerequisites + +- A Kubernetes cluster +- `kubectl` configured for that cluster +- Argo CD installed (including the ApplicationSet controller) +- Argo CD v2.6+ (required for `spec.sources` in the generated Application) + +## Apply one manifest + +```bash +kubectl apply -f examples/argocd/applicationset.yaml +``` + +That creates: + +- `ApplicationSet` `coder-k8s-stack` +- generated `Application` `coder-k8s-stack` + +## Ordering behavior + +This setup avoids app-of-apps ordering ambiguity by using a single generated `Application` with multiple sources. + +Resource-level sync waves provide dependency ordering for workload resources: + +- `examples/argocd/resources/00-coder-system-namespace.yaml` uses `wave -1` +- `examples/cloudnativepg/00-namespace.yaml` uses `wave 0` +- `examples/cloudnativepg/cnpg-cluster.yaml` uses `wave 1` +- `examples/cloudnativepg/codercontrolplane.yaml` uses `wave 2` + +## Watch rollout + +```bash +kubectl -n argocd get applications +kubectl -n coder wait --for=condition=Ready cluster/coder-db --timeout=10m +kubectl -n coder rollout status deployment/coder --timeout=10m +``` + +## Access Coder + +```bash +kubectl -n coder port-forward svc/coder 3000:80 +``` + +Open and complete the setup flow. + +## Customization + +- This example tracks `https://github.com/coder/coder-k8s.git` at `main`. + Update `repoURL` and `targetRevision` in `examples/argocd/applicationset.yaml` if you want to pin to a tag or use a fork. +- The CloudNativePG chart version is configurable with `cloudnativepgChartVersion`. + Pin it to an explicit chart version for reproducible environments. + +## Cleanup + +```bash +kubectl -n argocd delete applicationset coder-k8s-stack +``` + +The generated `Application` includes `resources-finalizer.argocd.argoproj.io` so deleting the `ApplicationSet` cascades cleanup of managed resources. Depending on your storage class reclaim policy, PVCs from PostgreSQL may remain after cleanup. diff --git a/examples/argocd/applicationset.yaml b/examples/argocd/applicationset.yaml new file mode 100644 index 00000000..187479e4 --- /dev/null +++ b/examples/argocd/applicationset.yaml @@ -0,0 +1,60 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: coder-k8s-stack + namespace: argocd + labels: + app.kubernetes.io/part-of: coder-k8s-example +spec: + goTemplate: true + goTemplateOptions: + - missingkey=error + generators: + - list: + elements: + - name: coder-k8s-stack + destinationServer: https://kubernetes.default.svc + destinationNamespace: cnpg-system + repoURL: https://github.com/coder/coder-k8s.git + targetRevision: main + cloudnativepgChartVersion: "*" + template: + metadata: + name: "{{ .name }}" + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + app.kubernetes.io/part-of: coder-k8s-example + spec: + project: default + sources: + - repoURL: https://cloudnative-pg.github.io/charts + chart: cloudnative-pg + targetRevision: "{{ .cloudnativepgChartVersion }}" + helm: + releaseName: cnpg + - repoURL: "{{ .repoURL }}" + targetRevision: "{{ .targetRevision }}" + path: examples/argocd/resources + - repoURL: "{{ .repoURL }}" + targetRevision: "{{ .targetRevision }}" + path: config/crd/bases + - repoURL: "{{ .repoURL }}" + targetRevision: "{{ .targetRevision }}" + path: config/rbac + - repoURL: "{{ .repoURL }}" + targetRevision: "{{ .targetRevision }}" + path: deploy + - repoURL: "{{ .repoURL }}" + targetRevision: "{{ .targetRevision }}" + path: examples/cloudnativepg + destination: + server: "{{ .destinationServer }}" + namespace: "{{ .destinationNamespace }}" + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/examples/argocd/resources/00-coder-system-namespace.yaml b/examples/argocd/resources/00-coder-system-namespace.yaml new file mode 100644 index 00000000..5b7b3efd --- /dev/null +++ b/examples/argocd/resources/00-coder-system-namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: coder-system + annotations: + argocd.argoproj.io/sync-wave: "-1" + labels: + app.kubernetes.io/part-of: coder-k8s-example diff --git a/examples/cloudnativepg/00-namespace.yaml b/examples/cloudnativepg/00-namespace.yaml index 67aec19c..8778e363 100644 --- a/examples/cloudnativepg/00-namespace.yaml +++ b/examples/cloudnativepg/00-namespace.yaml @@ -2,5 +2,7 @@ apiVersion: v1 kind: Namespace metadata: name: coder + annotations: + argocd.argoproj.io/sync-wave: "0" labels: app.kubernetes.io/part-of: coder-k8s-example diff --git a/examples/cloudnativepg/README.md b/examples/cloudnativepg/README.md index 87869a05..892f462d 100644 --- a/examples/cloudnativepg/README.md +++ b/examples/cloudnativepg/README.md @@ -46,6 +46,8 @@ kubectl apply -f examples/cloudnativepg/ The namespace manifest is prefixed (`00-namespace.yaml`) so `kubectl apply -f` creates `coder` before namespaced resources. +These manifests also include Argo CD sync-wave annotations so the same directory works with [`examples/argocd/`](../argocd/): namespace (`wave 0`) -> CloudNativePG `Cluster` (`wave 1`) -> `CoderControlPlane` (`wave 2`). + Wait for PostgreSQL and verify the generated Secret: ```bash diff --git a/examples/cloudnativepg/cnpg-cluster.yaml b/examples/cloudnativepg/cnpg-cluster.yaml index 6cc119d0..2996abc9 100644 --- a/examples/cloudnativepg/cnpg-cluster.yaml +++ b/examples/cloudnativepg/cnpg-cluster.yaml @@ -3,6 +3,9 @@ kind: Cluster metadata: name: coder-db namespace: coder + annotations: + argocd.argoproj.io/sync-wave: "1" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true spec: instances: 1 storage: diff --git a/examples/cloudnativepg/codercontrolplane.yaml b/examples/cloudnativepg/codercontrolplane.yaml index 6f0a9a8f..e6b425d0 100644 --- a/examples/cloudnativepg/codercontrolplane.yaml +++ b/examples/cloudnativepg/codercontrolplane.yaml @@ -3,6 +3,9 @@ kind: CoderControlPlane metadata: name: coder namespace: coder + annotations: + argocd.argoproj.io/sync-wave: "2" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true spec: replicas: 1 service: