# Technology Stack: CI/CD Testing, ArgoCD GitOps, and Observability

**Project:** TaskPlanner v2.0 Production Operations
**Researched:** 2026-02-03
**Scope:** Stack additions for existing k3s-deployed SvelteKit app

## Executive Summary

This research covers three areas: (1) adding tests to the existing Gitea Actions pipeline, (2) ArgoCD for GitOps deployment automation, and (3) Prometheus/Grafana/Loki observability. The existing setup already has ArgoCD configured; research focuses on validating that configuration and adding the observability stack.

**Key finding:** Promtail is EOL on 2026-03-02. Use Grafana Alloy instead for log collection.

---

## 1. CI/CD Testing Stack

### Recommended Stack

| Component | Version | Purpose | Rationale |
|-----------|---------|---------|-----------|
| Playwright | ^1.58.1 (existing) | E2E testing | Already configured, comprehensive browser automation |
| Vitest | ^3.0.0 | Unit/component tests | Official Svelte recommendation for Vite-based projects |
| @testing-library/svelte | ^5.0.0 | Component testing utilities | Streamlined component assertions |
| mcr.microsoft.com/playwright | v1.58.1 | CI browser execution | Pre-installed browsers, eliminates install step |

### Why This Stack

**Playwright (keep existing):** Already configured with `playwright.config.ts` and `tests/docker-deployment.spec.ts`. The existing tests cover critical paths: health endpoint, CSRF-protected form submissions, and data persistence. Extend rather than replace.

**Vitest (add):** Svelte officially recommends Vitest for unit and component testing when using Vite (which SvelteKit uses). Vitest shares Vite's config, eliminating configuration overhead. Jest muscle memory transfers directly.

**NOT recommended:**
- Jest: Requires separate configuration, slower than Vitest, no Vite integration
- Cypress: Overlaps with Playwright; adding both creates maintenance burden
- @vitest/browser with Playwright: Adds complexity; save for later if jsdom proves insufficient

### Gitea Actions Workflow Updates

The existing workflow at `.gitea/workflows/build.yaml` needs a test stage. Gitea Actions uses GitHub Actions syntax.

**Recommended workflow structure:**

```yaml
name: Build and Push

on:
  push:
    branches: [master, main]
  pull_request:
    branches: [master, main]

env:
  REGISTRY: git.kube2.tricnet.de
  IMAGE_NAME: tho/taskplaner

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.58.1-noble
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run type check
        run: npm run check

      - name: Run unit tests
        run: npm run test:unit

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          CI: true

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name != 'pull_request'
    steps:
      # ... existing build steps ...
```

**Key decisions:**
- Use Playwright Docker image to avoid browser installation (saves 2-3 minutes)
- Run tests before build to fail fast
- Only build/push on push to master, not PRs
- Type checking (`svelte-check`) catches errors before runtime

### Package.json Scripts to Add

```json
{
  "scripts": {
    "test": "npm run test:unit && npm run test:e2e",
    "test:unit": "vitest run",
    "test:unit:watch": "vitest",
    "test:e2e": "playwright test",
    "test:e2e:docker": "BASE_URL=http://localhost:3000 playwright test tests/docker-deployment.spec.ts"
  }
}
```

### Installation

```bash
# Add Vitest and testing utilities
npm install -D vitest @testing-library/svelte jsdom
```

### Vitest Configuration

Create `vitest.config.ts`:

```typescript
import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}'],
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/test-setup.ts']
  }
});
```

### Confidence: HIGH

Sources:
- [Svelte Testing Documentation](https://svelte.dev/docs/svelte/testing) - Official recommendation for Vitest
- [Playwright CI Setup](https://playwright.dev/docs/ci-intro) - Docker image and CI best practices
- Existing `playwright.config.ts` in project

---

## 2. ArgoCD GitOps Stack

### Current State

ArgoCD is already configured in `argocd/application.yaml`. The configuration is correct and follows best practices:

```yaml
syncPolicy:
  automated:
    prune: true      # Removes resources deleted from Git
    selfHeal: true   # Reverts manual changes
```

### Recommended Stack

| Component | Version | Purpose | Rationale |
|-----------|---------|---------|-----------|
| ArgoCD Helm Chart | 9.4.0 | GitOps controller | Latest stable, deploys ArgoCD v3.3.0 |

### What's Already Done (No Changes Needed)

1. **Application manifest:** `argocd/application.yaml` correctly points to `helm/taskplaner`
2. **Auto-sync enabled:** `automated.prune` and `selfHeal` are configured
3. **Git-based image tags:** Pipeline updates `values.yaml` with new image tag
4. **Namespace creation:** `CreateNamespace=true` is set

### What May Need Verification

1. **ArgoCD installation:** Verify ArgoCD is actually deployed on the k3s cluster
2. **Repository credentials:** If the Gitea repo is private, ArgoCD needs credentials
3. **Registry secret:** The `gitea-registry-secret` placeholder needs real credentials

### Installation (if ArgoCD not yet installed)

```bash
# Add ArgoCD Helm repository
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Install ArgoCD (minimal for single-node k3s)
helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --set server.service.type=ClusterIP \
  --set configs.params.server\.insecure=true  # If behind Traefik TLS termination
```

### Apply Application

```bash
kubectl apply -f argocd/application.yaml
```

### NOT Recommended

- **ArgoCD Image Updater:** Overkill for single-app deployment; the current approach of updating values.yaml in Git is simpler and provides better audit trail
- **ApplicationSets:** Unnecessary for single environment
- **App of Apps pattern:** Unnecessary complexity for one application

### Confidence: HIGH

Sources:
- [ArgoCD Helm Chart on Artifact Hub](https://artifacthub.io/packages/helm/argo/argo-cd) - Version 9.4.0 confirmed
- [ArgoCD Helm GitHub Releases](https://github.com/argoproj/argo-helm/releases) - Release notes
- Existing `argocd/application.yaml` in project

---

## 3. Observability Stack

### Recommended Stack

| Component | Chart | Version | Purpose |
|-----------|-------|---------|---------|
| kube-prometheus-stack | prometheus-community/kube-prometheus-stack | 81.4.2 | Prometheus + Grafana + Alertmanager |
| Loki | grafana/loki | 6.51.0 | Log aggregation (monolithic mode) |
| Grafana Alloy | grafana/alloy | 1.5.3 | Log collection agent |

### Why This Stack

**kube-prometheus-stack (not standalone Prometheus):** Single chart deploys Prometheus, Grafana, Alertmanager, node-exporter, and kube-state-metrics. Pre-configured with Kubernetes dashboards. This is the standard approach.

**Loki (not ELK/Elasticsearch):** "Like Prometheus, but for logs." Integrates natively with Grafana. Much lower resource footprint than Elasticsearch. Uses same label-based querying as Prometheus.

**Grafana Alloy (not Promtail):** CRITICAL - Promtail reaches End-of-Life on 2026-03-02 (next month). Grafana Alloy is the official replacement. It's based on OpenTelemetry Collector and supports logs, metrics, and traces in one agent.

### NOT Recommended

- **Promtail:** EOL 2026-03-02. Do not install; use Alloy
- **loki-stack Helm chart:** Deprecated, no longer maintained
- **Elasticsearch/ELK:** Resource-heavy, complex, overkill for single-user app
- **Loki microservices mode:** Requires 3+ nodes, object storage; overkill for personal app
- **Separate Prometheus + Grafana charts:** kube-prometheus-stack bundles them correctly

### Architecture

```
                                    +------------------+
                                    |     Grafana      |
                                    | (Dashboards/UI)  |
                                    +--------+---------+
                                             |
                        +--------------------+--------------------+
                        |                                         |
               +--------v---------+                    +----------v---------+
               |    Prometheus    |                    |       Loki         |
               |    (Metrics)     |                    |      (Logs)        |
               +--------+---------+                    +----------+---------+
                        |                                         |
         +--------------+---------------+                         |
         |              |               |                         |
   +-----v-----+  +-----v-----+  +------v------+         +--------v---------+
   |  node-    |  |  kube-    |  | TaskPlanner |         |   Grafana Alloy  |
   |  exporter |  |  state-   |  |   /metrics  |         |  (Log Shipper)   |
   |           |  |  metrics  |  |             |         |                  |
   +-----------+  +-----------+  +-------------+         +------------------+
```

### Installation

```bash
# Add Helm repositories
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# Create monitoring namespace
kubectl create namespace monitoring

# Install kube-prometheus-stack
helm install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --values prometheus-values.yaml

# Install Loki (monolithic mode for single-node)
helm install loki grafana/loki \
  --namespace monitoring \
  --values loki-values.yaml

# Install Alloy for log collection
helm install alloy grafana/alloy \
  --namespace monitoring \
  --values alloy-values.yaml
```

### Recommended Values Files

#### prometheus-values.yaml (minimal for k3s single-node)

```yaml
# Reduce resource usage for single-node k3s
prometheus:
  prometheusSpec:
    retention: 15d
    resources:
      requests:
        cpu: 200m
        memory: 512Mi
      limits:
        cpu: 1000m
        memory: 2Gi
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: longhorn  # Use existing Longhorn
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 20Gi

alertmanager:
  alertmanagerSpec:
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 256Mi
    storage:
      volumeClaimTemplate:
        spec:
          storageClassName: longhorn
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 5Gi

grafana:
  persistence:
    enabled: true
    storageClassName: longhorn
    size: 5Gi
  # Grafana will be exposed via Traefik
  ingress:
    enabled: true
    ingressClassName: traefik
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-prod
    hosts:
      - grafana.kube2.tricnet.de
    tls:
      - secretName: grafana-tls
        hosts:
          - grafana.kube2.tricnet.de

# Disable components not needed for single-node
kubeControllerManager:
  enabled: false  # k3s bundles this differently
kubeScheduler:
  enabled: false  # k3s bundles this differently
kubeProxy:
  enabled: false  # k3s uses different proxy
```

#### loki-values.yaml (monolithic mode)

```yaml
deploymentMode: SingleBinary

loki:
  auth_enabled: false
  commonConfig:
    replication_factor: 1
  storage:
    type: filesystem
  schemaConfig:
    configs:
      - from: "2024-01-01"
        store: tsdb
        object_store: filesystem
        schema: v13
        index:
          prefix: loki_index_
          period: 24h

singleBinary:
  replicas: 1
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 1Gi
  persistence:
    enabled: true
    storageClass: longhorn
    size: 10Gi

# Disable components not needed for monolithic
backend:
  replicas: 0
read:
  replicas: 0
write:
  replicas: 0

# Gateway not needed for internal access
gateway:
  enabled: false
```

#### alloy-values.yaml

```yaml
alloy:
  configMap:
    content: |-
      // Discover and collect logs from all pods
      discovery.kubernetes "pods" {
        role = "pod"
      }

      discovery.relabel "pods" {
        targets = discovery.kubernetes.pods.targets

        rule {
          source_labels = ["__meta_kubernetes_namespace"]
          target_label  = "namespace"
        }
        rule {
          source_labels = ["__meta_kubernetes_pod_name"]
          target_label  = "pod"
        }
        rule {
          source_labels = ["__meta_kubernetes_pod_container_name"]
          target_label  = "container"
        }
      }

      loki.source.kubernetes "pods" {
        targets    = discovery.relabel.pods.output
        forward_to = [loki.write.local.receiver]
      }

      loki.write "local" {
        endpoint {
          url = "http://loki.monitoring.svc:3100/loki/api/v1/push"
        }
      }

controller:
  type: daemonset

resources:
  requests:
    cpu: 50m
    memory: 64Mi
  limits:
    cpu: 200m
    memory: 256Mi
```

### TaskPlanner Metrics Endpoint

The app needs a `/metrics` endpoint for Prometheus to scrape. SvelteKit options:

1. **prom-client library** (recommended): Standard Prometheus client for Node.js
2. **Custom endpoint**: Simple counter/gauge implementation

Add to `package.json`:
```bash
npm install prom-client
```

Add ServiceMonitor for Prometheus to scrape TaskPlanner:

```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: taskplaner
  namespace: monitoring
  labels:
    release: prometheus  # Must match Prometheus selector
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: taskplaner
  namespaceSelector:
    matchNames:
      - default
  endpoints:
    - port: http
      path: /metrics
      interval: 30s
```

### Resource Summary

Total additional resource requirements for observability:

| Component | CPU Request | Memory Request | Storage |
|-----------|-------------|----------------|---------|
| Prometheus | 200m | 512Mi | 20Gi |
| Alertmanager | 50m | 64Mi | 5Gi |
| Grafana | 100m | 128Mi | 5Gi |
| Loki | 100m | 256Mi | 10Gi |
| Alloy (per node) | 50m | 64Mi | - |
| **Total** | ~500m | ~1Gi | 40Gi |

This fits comfortably on a single k3s node with 4+ cores and 8GB+ RAM.

### Confidence: HIGH

Sources:
- [kube-prometheus-stack on Artifact Hub](https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack) - Version 81.4.2
- [Grafana Loki Helm Installation](https://grafana.com/docs/loki/latest/setup/install/helm/) - Monolithic mode guidance
- [Grafana Alloy Kubernetes Deployment](https://grafana.com/docs/alloy/latest/set-up/install/kubernetes/) - Alloy setup
- [Promtail Deprecation Notice](https://grafana.com/docs/loki/latest/send-data/promtail/installation/) - EOL 2026-03-02
- [Migrate from Promtail to Alloy](https://grafana.com/docs/alloy/latest/set-up/migrate/from-promtail/) - Migration guide

---

## Summary: What to Install

### Immediate Actions

| Category | Add | Version | Notes |
|----------|-----|---------|-------|
| Testing | vitest | ^3.0.0 | Unit tests |
| Testing | @testing-library/svelte | ^5.0.0 | Component testing |
| Metrics | prom-client | ^15.0.0 | Prometheus metrics from app |

### Helm Charts to Deploy

| Chart | Repository | Version | Namespace |
|-------|------------|---------|-----------|
| kube-prometheus-stack | prometheus-community | 81.4.2 | monitoring |
| loki | grafana | 6.51.0 | monitoring |
| alloy | grafana | 1.5.3 | monitoring |

### Already Configured (Verify, Don't Re-install)

| Component | Status | Action |
|-----------|--------|--------|
| ArgoCD Application | Configured in `argocd/application.yaml` | Verify ArgoCD is running |
| Playwright | Configured in `playwright.config.ts` | Keep, extend tests |

### Do NOT Install

| Component | Reason |
|-----------|--------|
| Promtail | EOL 2026-03-02, use Alloy instead |
| loki-stack chart | Deprecated, unmaintained |
| Elasticsearch/ELK | Overkill, resource-heavy |
| Jest | Vitest is better for Vite projects |
| ArgoCD Image Updater | Current Git-based approach is simpler |

---

## Helm Repository Commands

```bash
# Add all needed repositories
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Verify
helm search repo prometheus-community/kube-prometheus-stack
helm search repo grafana/loki
helm search repo grafana/alloy
helm search repo argo/argo-cd
```

---

## Sources

### Official Documentation
- [Svelte Testing](https://svelte.dev/docs/svelte/testing)
- [Playwright CI Setup](https://playwright.dev/docs/ci-intro)
- [ArgoCD Helm Chart](https://artifacthub.io/packages/helm/argo/argo-cd)
- [kube-prometheus-stack](https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack)
- [Grafana Loki Helm](https://grafana.com/docs/loki/latest/setup/install/helm/)
- [Grafana Alloy](https://grafana.com/docs/alloy/latest/set-up/install/kubernetes/)

### Critical Updates
- [Promtail EOL Notice](https://grafana.com/docs/loki/latest/send-data/promtail/installation/) - EOL 2026-03-02
- [Promtail to Alloy Migration](https://grafana.com/docs/alloy/latest/set-up/migrate/from-promtail/)
