From 227839c27ad7fea78f16759d67a479b55a2049c0 Mon Sep 17 00:00:00 2001 From: rock Date: Mon, 28 Sep 2026 08:51:31 +0800 Subject: [PATCH] feat(poimen): consolidate into single multi-source ArgoCD app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merge poimen-workflows + poimen-memory + db + frontend into one app - Delete duplicate apps: poimen-workflows, poimen-memory-app (kubectl done) - Delete poimen-memory ArgoCD app file (replaced by multi-source source 2) - Delete poimen-memory-database (replaced by source 3 → k8s/infra/poimen/) - Move memory-db CNPG cluster to k8s/infra/poimen/ (isolated from other tenants) - Remove memory-db from k8s/infra/databases/kustomization.yaml - Add frontend manifests (Deployment, Service, Ingress) to k8s/apps/poimen/ - Add frontend NetworkPolicy: egress to api-gw (api:8080), memory (poimen:8080), Gotify (notifications:80), Authentik (iam:9000/9443), DNS (kube-system:53) - Sync-wave -1 on CNPG cluster ensures DB ready before app pods (wave 0) - Image updater: workflows + memory + frontend tracked in one annotation block - poimen-toolkit stays separate (CRD management, independent rollback) --- k8s/apps/poimen/frontend-deployment.yaml | 114 ++++++++++++++++++++ k8s/apps/poimen/frontend-ingress.yaml | 24 +++++ k8s/apps/poimen/frontend-networkpolicy.yaml | 78 ++++++++++++++ k8s/apps/poimen/frontend-service.yaml | 17 +++ k8s/apps/poimen/kustomization.yaml | 12 +++ k8s/argocd/apps/70-poimen-memory.yaml | 41 ------- k8s/argocd/apps/70-poimen.yaml | 67 ++++++++++-- k8s/infra/databases/kustomization.yaml | 1 - k8s/infra/poimen/kustomization.yaml | 8 ++ k8s/infra/poimen/memory-db.yaml | 37 +++++++ 10 files changed, 347 insertions(+), 52 deletions(-) create mode 100644 k8s/apps/poimen/frontend-deployment.yaml create mode 100644 k8s/apps/poimen/frontend-ingress.yaml create mode 100644 k8s/apps/poimen/frontend-networkpolicy.yaml create mode 100644 k8s/apps/poimen/frontend-service.yaml create mode 100644 k8s/apps/poimen/kustomization.yaml delete mode 100644 k8s/argocd/apps/70-poimen-memory.yaml create mode 100644 k8s/infra/poimen/kustomization.yaml create mode 100644 k8s/infra/poimen/memory-db.yaml diff --git a/k8s/apps/poimen/frontend-deployment.yaml b/k8s/apps/poimen/frontend-deployment.yaml new file mode 100644 index 0000000..c5cbd12 --- /dev/null +++ b/k8s/apps/poimen/frontend-deployment.yaml @@ -0,0 +1,114 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: poimen-frontend + namespace: poimen + labels: + app: poimen-frontend + app.kubernetes.io/name: poimen-frontend + app.kubernetes.io/component: frontend + app.kubernetes.io/part-of: poimen + annotations: + argocd.argoproj.io/sync-wave: "0" + reloader.stakater.com/auto: "true" +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: poimen-frontend + template: + metadata: + labels: + app: poimen-frontend + app.kubernetes.io/name: poimen-frontend + # Admitted by llm-serving-default-deny NetworkPolicy (port 8080). + # Required for server-side LLM calls through api-gw. + llm-client: "true" + spec: + imagePullSecrets: + - name: forgejo-registry + securityContext: + runAsNonRoot: true + runAsUser: 1001 + fsGroup: 1001 + containers: + - name: frontend + image: forgejo.riotpiao.com/riotpiao-poimen/poimen-frontend:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 3000 + protocol: TCP + env: + # In-cluster api-gw (server-side only — never exposed to browser) + - name: LLM_API_URL + value: "http://api-gateway.api.svc.cluster.local:8080" + # Memory service (co-located in poimen namespace) + - name: POIMEN_MEMORY_URL + valueFrom: + configMapKeyRef: + name: poimen-config + key: POIMEN_MEMORY_URL + # OIDC — uses same client as existing poimen services + - name: LLM_OIDC_TOKEN_URL + valueFrom: + secretKeyRef: + name: poimen-secrets + key: OIDC_TOKEN_URL + - name: LLM_OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: poimen-oauth2-creds + key: client_id + - name: LLM_OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: poimen-oauth2-creds + key: client_secret + - name: LLM_OIDC_SCOPE + value: "openid llm_inference" + # NextAuth + - name: NEXTAUTH_URL + value: "https://poimen.riotpiao.com" + - name: NEXTAUTH_SECRET + valueFrom: + secretKeyRef: + name: poimen-secrets + key: NEXTAUTH_SECRET + # Gotify (server-side notifications + HITL messages) + - name: GOTIFY_URL + value: "http://gotify.notifications.svc.cluster.local" + - name: GOTIFY_TOKEN + valueFrom: + secretKeyRef: + name: poimen-secrets + key: GOTIFY_TOKEN + # Public env — embedded in browser bundle at build time + - name: NEXT_PUBLIC_API_URL + value: "https://api.riotpiao.com" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + failureThreshold: 3 diff --git a/k8s/apps/poimen/frontend-ingress.yaml b/k8s/apps/poimen/frontend-ingress.yaml new file mode 100644 index 0000000..204f817 --- /dev/null +++ b/k8s/apps/poimen/frontend-ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: poimen-frontend + namespace: poimen + annotations: + # Next.js SSE + LLM streaming responses need long timeouts + no buffering. + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-body-size: "10m" +spec: + ingressClassName: nginx + rules: + - host: poimen.riotpiao.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: poimen-frontend + port: + number: 3000 diff --git a/k8s/apps/poimen/frontend-networkpolicy.yaml b/k8s/apps/poimen/frontend-networkpolicy.yaml new file mode 100644 index 0000000..04b93a6 --- /dev/null +++ b/k8s/apps/poimen/frontend-networkpolicy.yaml @@ -0,0 +1,78 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: poimen-frontend-egress + namespace: poimen + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + podSelector: + matchLabels: + app: poimen-frontend + policyTypes: + - Ingress + - Egress + + ingress: + # Allow from ingress-nginx controller → frontend on port 3000 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - protocol: TCP + port: 3000 + + egress: + # DNS — required for all in-cluster service resolution + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + # api-gw (api namespace) — all server-side LLM + workflow + memory calls + # api-gw NetworkPolicy already allows ingress from poimen namespace. + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: api + ports: + - protocol: TCP + port: 8080 + + # Memory service (co-located in poimen namespace) — GraphQL session queries + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: poimen + podSelector: + matchLabels: + app.kubernetes.io/name: poimen-memory + ports: + - protocol: TCP + port: 8080 + + # Gotify (notifications namespace) — /api/notify/messages server-side poll + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: notifications + ports: + - protocol: TCP + port: 80 + + # Authentik (iam namespace) — OIDC token fetch + JWKS verification + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: iam + ports: + - protocol: TCP + port: 9000 + - protocol: TCP + port: 9443 diff --git a/k8s/apps/poimen/frontend-service.yaml b/k8s/apps/poimen/frontend-service.yaml new file mode 100644 index 0000000..22102d3 --- /dev/null +++ b/k8s/apps/poimen/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: poimen-frontend + namespace: poimen + labels: + app: poimen-frontend + app.kubernetes.io/name: poimen-frontend +spec: + type: ClusterIP + selector: + app: poimen-frontend + ports: + - name: http + port: 3000 + targetPort: http + protocol: TCP diff --git a/k8s/apps/poimen/kustomization.yaml b/k8s/apps/poimen/kustomization.yaml new file mode 100644 index 0000000..59c2980 --- /dev/null +++ b/k8s/apps/poimen/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Poimen frontend — Deployment, Service, Ingress for poimen.riotpiao.com. +# Part of the `poimen` ArgoCD Application (multi-source, source 4). +# All resources deploy into namespace `poimen` (same as workers and memory). +# api-gw NetworkPolicy already allows ingress from namespace poimen. +# Memory service is co-located — no additional NetworkPolicy needed. +resources: + - frontend-deployment.yaml + - frontend-service.yaml + - frontend-ingress.yaml + - frontend-networkpolicy.yaml diff --git a/k8s/argocd/apps/70-poimen-memory.yaml b/k8s/argocd/apps/70-poimen-memory.yaml deleted file mode 100644 index 38df8a7..0000000 --- a/k8s/argocd/apps/70-poimen-memory.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Wave 7 — poimen-memory k8s resources (namespace poimen). -# -# Manages: Deployment, Service, ConfigMap, Redis cache, NetworkPolicies, -# ServiceMonitor, and any future k8s infra owned by the memory service. -# Image updates handled by argocd-image-updater (digest strategy, :latest tag). -# -# Source: riotpiao-poimen/poimen-memory.git k8s/app/ -# Kustomization at k8s/app/kustomization.yaml lists all managed resources. -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: poimen-memory - namespace: argocd - annotations: - argocd.argoproj.io/sync-wave: "7" - argocd-image-updater.argoproj.io/image-list: | - memory=forgejo.riotpiao.com/riotpiao-poimen/poimen-memory - argocd-image-updater.argoproj.io/memory.update-strategy: digest - argocd-image-updater.argoproj.io/memory.allow-tags: regexp:^latest$ - argocd-image-updater.argoproj.io/write-back-method: argocd -spec: - project: homelab - source: - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/poimen-memory.git - targetRevision: main - path: k8s/app - destination: - server: https://kubernetes.default.svc - namespace: poimen - syncPolicy: - automated: - prune: true - selfHeal: true - syncOptions: - - CreateNamespace=true - retry: - limit: 5 - backoff: - duration: 5s - factor: 2 - maxDuration: 3m diff --git a/k8s/argocd/apps/70-poimen.yaml b/k8s/argocd/apps/70-poimen.yaml index fe50ce5..1a40cfd 100644 --- a/k8s/argocd/apps/70-poimen.yaml +++ b/k8s/argocd/apps/70-poimen.yaml @@ -1,37 +1,84 @@ +# Poimen — unified ArgoCD Application (wave 7). +# +# Consolidates what was previously five separate apps: +# poimen → source 1 (poimen-workflows workers) +# poimen-workflows → duplicate of source 1 (deleted) +# poimen-memory → source 2 (memory service + redis) +# poimen-memory-app → duplicate of source 2 (deleted) +# poimen-memory-database → source 3 (CNPG cluster, moved to k8s/infra/poimen) +# +# poimen-toolkit is intentionally kept as a separate Application because it +# manages cluster-scoped CRDs (ToolDefinition). A bad CRD can affect the +# whole cluster; keeping it separate means it has its own sync history, +# independent rollback, and can be paused without touching app workloads. +# +# Sync ordering via argocd sync-wave annotations: +# wave -1 → DB (CNPG Cluster) — must be Ready before app pods start +# wave 0 → all application Deployments (default) +# +# Image Updater tracks all four images. Write-back via argocd (in-cluster +# annotation patch — no git commit per image update). apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: poimen namespace: argocd + labels: + app.kubernetes.io/part-of: poimen annotations: argocd.argoproj.io/sync-wave: "7" - # Image Updater: auto-update on new image push (SHA tag filter) + # ── Image Updater ───────────────────────────────────────────────────────── argocd-image-updater.argoproj.io/image-list: | - memory=forgejo.riotpiao.com/rock/poimen-memory - workflows=forgejo.riotpiao.com/rock/poimen-workflows - frontend=forgejo.riotpiao.com/rock/poimen-frontend - argocd-image-updater.argoproj.io/memory.update-strategy: digest - argocd-image-updater.argoproj.io/memory.allow-tags: regexp:^latest$ + workflows=forgejo.riotpiao.com/riotpiao-poimen/poimen-workflows + memory=forgejo.riotpiao.com/riotpiao-poimen/poimen-memory + frontend=forgejo.riotpiao.com/riotpiao-poimen/poimen-frontend argocd-image-updater.argoproj.io/workflows.update-strategy: digest argocd-image-updater.argoproj.io/workflows.allow-tags: regexp:^latest$ + argocd-image-updater.argoproj.io/memory.update-strategy: digest + argocd-image-updater.argoproj.io/memory.allow-tags: regexp:^latest$ argocd-image-updater.argoproj.io/frontend.update-strategy: digest argocd-image-updater.argoproj.io/frontend.allow-tags: regexp:^latest$ argocd-image-updater.argoproj.io/write-back-method: argocd spec: project: homelab - source: - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/poimen-workflows.git - targetRevision: main - path: k8s + # Multi-source: each repo owns its own k8s manifests. + # ArgoCD syncs all sources in parallel; sync-wave annotations on individual + # resources control ordering within the sync operation. + sources: + # 1. Workers — Temporal activity + workflow workers, pi-worker pool + - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/poimen-workflows.git + targetRevision: main + path: k8s + + # 2. Memory service — poimen-memory API, Redis cache, NetworkPolicies + - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/poimen-memory.git + targetRevision: main + path: k8s/app + + # 3. Database infrastructure — CNPG Postgres cluster (wave -1) + # Moved from k8s/infra/databases to isolate poimen DB from other tenants. + - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git + targetRevision: main + path: k8s/infra/poimen + + # 4. Frontend — poimen.riotpiao.com (Deployment, Service, Ingress) + # Manifests live in homelab so all infra is in one place (Q4: C). + # Image tag managed by Image Updater above. + - repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git + targetRevision: main + path: k8s/apps/poimen + destination: server: https://kubernetes.default.svc namespace: poimen + syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true + - SkipDryRunOnMissingResource=true retry: limit: 5 backoff: diff --git a/k8s/infra/databases/kustomization.yaml b/k8s/infra/databases/kustomization.yaml index c32c72e..ff75276 100644 --- a/k8s/infra/databases/kustomization.yaml +++ b/k8s/infra/databases/kustomization.yaml @@ -8,5 +8,4 @@ resources: - namespaces.yaml - authentik-db.yaml - temporal-db.yaml - - memory-db.yaml - paperless-db.yaml diff --git a/k8s/infra/poimen/kustomization.yaml b/k8s/infra/poimen/kustomization.yaml new file mode 100644 index 0000000..a089cc3 --- /dev/null +++ b/k8s/infra/poimen/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Poimen infrastructure — CNPG Postgres for memory service. +# Part of the `poimen` ArgoCD Application (multi-source, source 3). +# Sync-wave -1 ensures the DB cluster is healthy before application +# deployments (wave 0) are applied. +resources: + - memory-db.yaml diff --git a/k8s/infra/poimen/memory-db.yaml b/k8s/infra/poimen/memory-db.yaml new file mode 100644 index 0000000..8811498 --- /dev/null +++ b/k8s/infra/poimen/memory-db.yaml @@ -0,0 +1,37 @@ +# Dedicated CNPG Postgres for Poimen Memory (GitOps, wave 2). +# Uses default longhorn storage class (3 replicas, dataLocality disabled). +# CNPG generates secret `memory-db-app` + service `memory-db-rw` in ns poimen. +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: memory-db + namespace: poimen + annotations: + argocd.argoproj.io/sync-wave: "-1" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + instances: 3 + imageName: ghcr.io/cloudnative-pg/postgresql:16.2 + bootstrap: + initdb: + database: memory + owner: app + encoding: UTF8 + localeCollate: C + localeCType: C + postInitApplicationSQL: + - "CREATE EXTENSION vector;" + enableSuperuserAccess: false + resources: + requests: { memory: "512Mi", cpu: "250m" } + limits: { memory: "2Gi", cpu: "1" } + storage: + size: 20Gi + storageClass: longhorn + affinity: + podAntiAffinityType: preferred + topologyKey: kubernetes.io/hostname + tolerations: + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule