diff --git a/Brewfile b/Brewfile index d4186a6..2f962c5 100644 --- a/Brewfile +++ b/Brewfile @@ -1,4 +1,5 @@ # CLI tools for blumeops management +brew "argocd" # ArgoCD CLI for GitOps management brew "bat" # Syntax-highlighted file concatenation brew "tea" # Gitea/Forgejo CLI for forge.tail8d86e.ts.net brew "podman" # Container CLI (uses VM on macOS, for building/pushing images) diff --git a/argocd/apps/apps.yaml b/argocd/apps/apps.yaml new file mode 100644 index 0000000..32a0e4f --- /dev/null +++ b/argocd/apps/apps.yaml @@ -0,0 +1,24 @@ +# App-of-apps root Application +# Watches argocd/apps/ and creates/manages all Application resources +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: apps + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: feature/k8s-phase1-kickoff + path: argocd/apps + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + # Auto-sync enabled: new/changed Application manifests appear automatically + # but child apps still require manual sync (they have manual sync policy) diff --git a/argocd/apps/argocd.yaml b/argocd/apps/argocd.yaml new file mode 100644 index 0000000..b737160 --- /dev/null +++ b/argocd/apps/argocd.yaml @@ -0,0 +1,20 @@ +# ArgoCD self-management Application +# After bootstrap, ArgoCD manages its own deployment +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: argocd + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: feature/k8s-phase1-kickoff + path: argocd/manifests/argocd + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + syncOptions: + - CreateNamespace=true + # Manual sync only - no automated sync on git push diff --git a/argocd/apps/tailscale-operator.yaml b/argocd/apps/tailscale-operator.yaml index 041f676..5d9c6c4 100644 --- a/argocd/apps/tailscale-operator.yaml +++ b/argocd/apps/tailscale-operator.yaml @@ -7,6 +7,12 @@ metadata: namespace: argocd spec: project: default + # Tailscale operator mutates externalName from "placeholder" to actual proxy service + ignoreDifferences: + - group: "" + kind: Service + jsonPointers: + - /spec/externalName source: repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git targetRevision: feature/k8s-phase1-kickoff @@ -15,8 +21,6 @@ spec: server: https://kubernetes.default.svc namespace: tailscale syncPolicy: - automated: - prune: true - selfHeal: true syncOptions: - CreateNamespace=true + # Manual sync only - no automated sync on git push diff --git a/argocd/manifests/argocd/README.md b/argocd/manifests/argocd/README.md index b6267cd..5e27c2e 100644 --- a/argocd/manifests/argocd/README.md +++ b/argocd/manifests/argocd/README.md @@ -1,28 +1,99 @@ # ArgoCD -GitOps continuous delivery for Kubernetes. +GitOps continuous delivery for Kubernetes, with self-management via ArgoCD. -## Installation +## Prerequisites -Uses kustomize with remote base from upstream ArgoCD. +- Tailscale operator deployed (see `argocd/manifests/tailscale-operator/README.md`) +- Deploy key added to forge for SSH access to blumeops repo + +## Manual Bootstrap + +Bootstrap is required when setting up a new cluster. After bootstrap, ArgoCD manages itself. ```bash -# Create namespace +# 1. Create namespace kubectl create namespace argocd -# Apply manifests +# 2. Apply ArgoCD manifests via kustomize kubectl apply -k argocd/manifests/argocd/ + +# 3. Wait for ArgoCD to be ready +kubectl wait --for=condition=available deployment/argocd-server -n argocd --timeout=300s + +# 4. Get initial admin password +kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo + +# 5. Login and change password +argocd login argocd.tail8d86e.ts.net --username admin --grpc-web +argocd account update-password + +# 6. Apply repo-forge secret for SSH access to forge +PRIV_KEY=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key?ssh-format=openssh")$'\n' && \ +kubectl create secret generic repo-forge -n argocd \ + --from-literal=type=git \ + --from-literal=url='ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git' \ + --from-literal=insecure=true \ + --from-literal=sshPrivateKey="$PRIV_KEY" && \ +kubectl label secret repo-forge -n argocd argocd.argoproj.io/secret-type=repository + +# 7. Apply ArgoCD Applications (self-management + app-of-apps) +kubectl apply -f argocd/apps/argocd.yaml +kubectl apply -f argocd/apps/apps.yaml ``` +After step 7, ArgoCD manages itself and all applications defined in `argocd/apps/`. + ## Access - URL: https://argocd.tail8d86e.ts.net - Username: `admin` -- Password: Get from secret (see below) +- Password: Stored in 1Password after initial setup + +## ArgoCD CLI Commands ```bash -# Get initial admin password -kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d +# Check all applications +argocd app list + +# Sync a specific application +argocd app sync + +# Check application status +argocd app get + +# Hard refresh (clear git cache) +argocd app get --hard-refresh +``` + +## Adding New Applications + +1. Create an Application manifest in `argocd/apps/.yaml` +2. Commit and push to forge +3. ArgoCD (via app-of-apps) automatically picks it up + +Example Application: +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: my-app + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/my-app + destination: + server: https://kubernetes.default.svc + namespace: my-app + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true ``` ## Files @@ -32,10 +103,12 @@ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.pas | `kustomization.yaml` | References upstream install.yaml + local customizations | | `service-tailscale.yaml` | Tailscale Ingress for external access with Let's Encrypt TLS | | `argocd-cmd-params-cm.yaml` | Patch to disable HTTPS redirect (TLS terminates at Ingress) | +| `repo-forge-secret.yaml.tpl` | Template documenting the forge SSH secret (manual) | | `README.md` | This file | -## Post-Setup +## Notes -1. Login and change the admin password -2. Store new password in 1Password -3. Configure git repository connection to `github.com/eblume/blumeops` +- **TODO:** Secrets (`repo-forge`) are not managed by ArgoCD and must be applied manually. + Future improvement: integrate with a secrets operator (e.g., External Secrets). +- ArgoCD uses Tailscale Ingress with Let's Encrypt for TLS termination. +- The `--grpc-web` flag is required for CLI access through the Tailscale ingress. diff --git a/argocd/manifests/tailscale-operator/README.md b/argocd/manifests/tailscale-operator/README.md index 5b2ffda..271a158 100644 --- a/argocd/manifests/tailscale-operator/README.md +++ b/argocd/manifests/tailscale-operator/README.md @@ -1,8 +1,6 @@ # Tailscale Kubernetes Operator -Manifests for the Tailscale Kubernetes Operator, sourced from Tailscale's official repository. - -**Note:** These are currently raw manifests from Tailscale, not yet kustomized. Once kustomized, this directory will include a `kustomization.yaml` and any necessary patches. +Manifests for the Tailscale Kubernetes Operator, managed via ArgoCD. ## Source @@ -18,26 +16,43 @@ Manifests for the Tailscale Kubernetes Operator, sourced from Tailscale's offici - Services: Write 2. ACL with `tag:k8s-operator` owning `tag:k8s` (so operator can tag resources it creates) -## Deployment +## Manual Bootstrap (Before ArgoCD) + +Tailscale operator must be deployed before ArgoCD since ArgoCD uses Tailscale for ingress. ```bash # 1. Create namespace kubectl create namespace tailscale -# 2. Apply the OAuth secret (uses 1Password for credential resolution) +# 2. Apply OAuth secret (uses 1Password) op inject -i argocd/manifests/tailscale-operator/secret.yaml.tpl | kubectl apply -f - -# 3. Apply the operator -kubectl apply -f argocd/manifests/tailscale-operator/operator.yaml - -# 4. Apply the ProxyClass (required for CRI-O image compatibility) -kubectl apply -f argocd/manifests/tailscale-operator/proxyclass.yaml +# 3. Apply manifests via kustomize +kubectl apply -k argocd/manifests/tailscale-operator/ ``` -**Important:** Services using the Tailscale LoadBalancer must reference the ProxyClass: -```yaml -annotations: - tailscale.com/proxy-class: "default" +## Ongoing Management (After ArgoCD) + +Once ArgoCD is running, the operator is managed by the `tailscale-operator` ArgoCD Application. +ArgoCD pulls manifests from forge and applies them automatically. + +## ArgoCD CLI Commands + +```bash +# Check application status +argocd app get tailscale-operator + +# Trigger a sync (pull latest from forge and apply) +argocd app sync tailscale-operator + +# Preview what would change without applying +argocd app diff tailscale-operator + +# View deployment history +argocd app history tailscale-operator + +# Hard refresh (clear cache and re-fetch from git) +argocd app get tailscale-operator --hard-refresh ``` ## Verification @@ -54,7 +69,22 @@ kubectl logs -n tailscale -l app.kubernetes.io/name=operator | File | Description | |------|-------------| +| `kustomization.yaml` | Kustomize configuration for all manifests | | `operator.yaml` | Operator deployment, CRDs, RBAC (secret removed) | | `proxyclass.yaml` | ProxyClass with fully-qualified images for CRI-O | -| `secret.yaml.tpl` | 1Password template for OAuth credentials | +| `dnsconfig.yaml` | DNSConfig for cluster-to-tailnet name resolution | +| `egress-forge.yaml` | Egress proxy for accessing forge on indri | +| `secret.yaml.tpl` | 1Password template for OAuth credentials (manual) | | `README.md` | This file | + +## Notes + +- **TODO:** The OAuth secret (`operator-oauth`) is not managed by ArgoCD and must be applied + manually. Future improvement: integrate with a secrets operator (e.g., External Secrets). +- Services using the Tailscale LoadBalancer must reference the ProxyClass: + ```yaml + annotations: + tailscale.com/proxy-class: "default" + ``` +- The egress proxy for forge targets `indri.tail8d86e.ts.net` directly (not `forge.tail8d86e.ts.net`) + because Tailscale Serve hostnames are virtual and only work via the Tailscale client. diff --git a/argocd/manifests/tailscale-operator/kustomization.yaml b/argocd/manifests/tailscale-operator/kustomization.yaml new file mode 100644 index 0000000..f0517ad --- /dev/null +++ b/argocd/manifests/tailscale-operator/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: tailscale + +resources: + - operator.yaml + - proxyclass.yaml + - dnsconfig.yaml + - egress-forge.yaml + +# Note: OAuth secret (operator-oauth) is NOT included here. +# It must be manually applied before deploying - see README.md