migrate from flux to argocd

- install argocd at home/cluster/argocd/, app-of-apps via root-app.yaml
- 4 child Applications: aprs, gridmap, prop, towerops
- argo.w5isp.com via cloudflared tunnel (CNAME + ingress_rule in tofu)
- update CLAUDE.md: drop K3s/Wavelog/n8n/Node-RED, document GitOps + ArgoCD
This commit is contained in:
Graham McIntire 2026-04-29 10:26:33 -05:00
parent c3ae90aff0
commit ae5ceebf44
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
10 changed files with 24704 additions and 64 deletions

View file

@ -28,7 +28,7 @@ ansible-playbook playbook.yml --tags provision,bind9
# - Storage: local-lvm (for BIND9 and general infrastructure VMs)
# - Network: vmbr0
#
# Note: Talos K3s nodes use different template and Longhorn for persistent storage
# Note: Talos nodes use a different template and Longhorn for persistent storage
```
### Ansible Operations
@ -62,20 +62,6 @@ tofu apply
tofu fmt
```
### Kubernetes Operations (K3s cluster at 204.110.191.2)
```bash
# Set kubeconfig
export KUBECONFIG=/Users/graham/dev/infra/home/ansible/kubeconfig
# Deploy APRS.me application
cd home/cluster/aprs
./deploy.sh
# Check deployment status
kubectl get pods -n aprs
kubectl get svc -n aprs
```
### Talos Operations (Home Cluster)
```bash
# Change to talos directory
@ -117,8 +103,8 @@ talosctl --talosconfig talosconfig logs -f kubelet
- `home/` - Home lab configurations
- `cluster/` - Kubernetes application deployments
- `aprs/` - APRS.me deployment with PostgreSQL/PostGIS and Redis
- `wavelog/` - Wavelog amateur radio logging application
- `argocd/` - ArgoCD install + Application manifests (app-of-apps via `root-app.yaml`)
- `aprs/` - APRS.me deployment manifests (legacy; live manifests in the `aprs.me` repo's `k8s/`)
### Key Infrastructure Services
@ -201,35 +187,25 @@ tofu plan
- `controlplane.yaml` / `worker.yaml` - Base node configs
- `configs/talos-worker{1,2,3}.yaml` - Per-node worker configs (with disk/extension settings)
- `configs/talos-cp{1,2,3}.yaml` - Per-node control plane configs
- **Applications**: `/cluster/` directory contains Kubernetes manifests
- **APRS.me** (namespace `aprs`): Amateur radio APRS tracking app (https://aprs.me)
- Deployment `aprs` (2 replicas), PostgreSQL 17 + PostGIS, Redis
- **Managed by Flux**: Kustomization `aprs-app` in `flux-system`, sourced from `https://git.mcintire.me/graham/aprs.me.git` — scaling won't stick unless Flux is suspended first
- Deploy manifests in `home/cluster/aprs/`
- **Status: stopped** (2026-04-19): Flux Kustomization `aprs-app` suspended and deployment scaled to 0
- Restart: `flux resume kustomization aprs-app -n flux-system` (Flux reconciles replicas back to 2)
- **GitOps**: ArgoCD (replaced Flux on 2026-04-29). See `home/cluster/argocd/` and the GitOps section below.
- **Applications**: deployed via ArgoCD `Application` manifests in `home/cluster/argocd/apps/`
- **APRS.me** (namespace `aprs`, https://aprs.me) — sourced from `git.mcintire.me/graham/aprs.me.git` `./k8s`
- **gridmap** (namespace `gridmap`) — sourced from `git.mcintire.me/graham/gridmap.git` `./k8s`
- **prop** (namespace `prop`) — sourced from `git.mcintire.me/graham/prop.git` `./k8s`
- **towerops** (namespace `towerops`) — sourced from `git.mcintire.me/graham/towerops-web.git` `./k8s`
- **Documentation**: See `/talos/README.md` and `/cluster/README.md` for detailed operations
- **Config Notes**:
- `install.extraKernelArgs` and `install.grubUseUKICmdline` cannot be used together
- Worker disks with existing data (e.g. old Ceph LVM) must be wiped before Talos will partition them: `talosctl reset --user-disks-to-wipe /dev/sdb --wipe-mode user-disks --reboot`
### K3s Cluster at 204.110.191.2
- **Purpose**: Home lab Kubernetes cluster
- **Node1 IPs**:
- Public: 204.110.191.2
- Tailscale: 100.102.235.20
- Internal: 10.0.101.21
- **API Access**: Currently only available on 10.0.101.21:6443 (not on Tailscale)
- **Context**: Use `kubectl config use-context homelab` to access
- **Storage**: Longhorn for persistent volumes
- **Applications**:
- **Wavelog**: Amateur radio logging application (https://log.w5isp.com)
- MariaDB 11 database (10GB storage)
- 20GB storage for configs, backups, uploads, QSL cards
- PHP application with custom limits (50MB uploads, 256MB memory)
- Deploy with: `cd home/cluster/wavelog && ./deploy.sh`
### GitOps via ArgoCD (home-cluster)
- **Install**: `home/cluster/argocd/install/` — upstream ArgoCD manifests + a Kustomize patch enabling `server.insecure=true` (cloudflared terminates TLS) and `reposerver.git.modules.enabled=false` (the `aprs.me` repo has a stale submodule pin that breaks fetch otherwise)
- **App-of-apps**: `home/cluster/argocd/root-app.yaml` watches `home/cluster/argocd/apps/` in this repo and reconciles all child Applications. Add a new YAML to `apps/` and push — Argo picks it up.
- **Repo creds**: `forgejo-mcintire-creds` Secret in `argocd` ns is a credential template for `https://git.mcintire.me/graham` — username/password copied from `flux-system/forgejo-git-credentials`.
- **UI**: `argo.w5isp.com` via cloudflared tunnel → `argocd-server.argocd.svc.cluster.local:80`. Initial admin password is in `argocd/argocd-initial-admin-secret`.
- **Image auto-update**: Flux's `ImagePolicy`/`ImageUpdateAutomation` is gone. Annotations like `# {"$imagepolicy": "flux-system:prop"}` in app repo manifests are inert. To restore, install `argocd-image-updater`.
### New Server: w5isp.w5isp.com (204.110.191.200)
### Caddy server: w5isp.w5isp.com (204.110.191.200)
- **Purpose**: Single-node server running Debian 13
- **Features**:
- Web services with automatic SSL
@ -252,35 +228,22 @@ tofu plan
### Notes for Claude
- As you learn new things, keep claude.md updated
## Clusters
- **Talos Home Cluster**: 10.0.15.1-6 (primary home lab cluster)
- **K3s Cluster**: 204.110.191.2 (legacy, see above for details)
## Cluster
- **home-cluster** (Talos): 10.0.15.1-6 — only Kubernetes cluster. External traffic enters via the `cloudflared` tunnel deployment in the `cloudflared` namespace; per-hostname routing rules live in Cloudflare's dashboard, not in the cluster.
## Application Deployment Patterns
### Standard App Deployment Process
1. **Check for existing manifests** in `/home/cluster/[app-name]/`
2. **Set namespace security policy**: `kubectl label namespace [app] pod-security.kubernetes.io/enforce=privileged --overwrite`
3. **Use ClusterIP services** (internal only) with Traefik for external access
4. **Deploy via existing deploy.sh** scripts when available
5. **Use Longhorn storage** for persistent volumes
6. **Configure Traefik IngressRoute** for SSL and routing
### Deployed Applications
- **APRS.me**: https://aprs.me (Talos home-cluster, namespace `aprs`, currently scaled to 0)
- **Node-RED**: https://nodered.w5isp.com (automation platform)
- **n8n**: https://n8n.w5isp.com (workflow automation, PostgreSQL backend)
- **Wavelog**: https://log.w5isp.com (amateur radio logging, MariaDB backend)
1. App repos contain their own k8s manifests in `./k8s/`
2. ArgoCD `Application` in `home/cluster/argocd/apps/<name>.yaml` points at the repo
3. Push the app repo → Argo auto-syncs (when `automated.selfHeal=true` is set)
4. Use Longhorn for persistent volumes (default StorageClass)
5. Use ClusterIP services + Cloudflare tunnel for external access
### Common Issues & Solutions
- **PodSecurity violations**: Set namespace to privileged security policy
- **PodSecurity violations**: Set namespace to privileged: `kubectl label namespace [ns] pod-security.kubernetes.io/enforce=privileged --overwrite`
- **Database connectivity**: Use short service names (`postgres` not `postgres.namespace.svc.cluster.local`)
- **Init container permissions**: Required for volume ownership fixes
- **SSL certificates**: cert-manager with Let's Encrypt, handled automatically via Traefik
- **Service DNS**: Services accessible via `service-name` within same namespace
### Traefik Configuration
- **Host-based routing**: Multiple apps on same IP (204.110.191.2)
- **Automatic SSL**: cert-manager integration with Let's Encrypt
- **HTTP to HTTPS redirect**: Configured in IngressRoute manifests
- **ClusterIP backend**: All apps use ClusterIP, Traefik provides external access
- **Argo can't fetch a repo with a submodule**: confirm `reposerver.git.modules.enabled=false` is set on `argocd-cmd-params-cm` (the `aprs.me` repo had a stale submodule pin that broke clones)
- **Argo refuses repo for "out-of-bounds symlinks"**: the repo has a tracked symlink pointing outside the working tree (e.g. into `/nix/store/...`). Fix in the source repo — Argo has no override.

View file

@ -0,0 +1,19 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: aprs
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.mcintire.me/graham/aprs.me.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: aprs
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,22 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gridmap
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.mcintire.me/graham/gridmap.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: gridmap
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,22 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prop
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.mcintire.me/graham/prop.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: prop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,19 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: towerops
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.mcintire.me/graham/towerops-web.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: towerops
syncPolicy:
syncOptions:
- CreateNamespace=true

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: argocd
resources:
- argocd-install.yaml
patches:
- target:
kind: ConfigMap
name: argocd-cmd-params-cm
patch: |-
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
data:
server.insecure: "true"
reposerver.git.modules.enabled: "false"

View file

@ -0,0 +1,22 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.mcintire.me/graham/infra.git
targetRevision: main
path: home/cluster/argocd/apps
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true

View file

@ -539,6 +539,10 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "towerops" {
hostname = "prop.w5isp.com"
service = "http://prop.prop.svc.cluster.local:5000"
}
ingress_rule {
hostname = "argo.w5isp.com"
service = "http://argocd-server.argocd.svc.cluster.local:80"
}
ingress_rule {
service = "http_status:404"
}
@ -688,6 +692,14 @@ resource "cloudflare_record" "w5isp_cname_prop" {
proxied = true
}
resource "cloudflare_record" "w5isp_cname_argo" {
zone_id = cloudflare_zone.w5isp_com.id
type = "CNAME"
name = "argo"
content = "d3d819ba-925c-4d84-8052-68ed32b686bb.cfargotunnel.com"
proxied = true
}
# =============================================================================
# w5isp.com - MX records
# =============================================================================

File diff suppressed because one or more lines are too long