barycenter/docs/kubernetes-deployment.md
Till Wegmueller a1056bb237
feat: add admin GraphQL API, background jobs, and user sync CLI
Major Features:
- Admin GraphQL API with dual endpoints (Seaography + custom)
- Background job scheduler with execution tracking
- Idempotent user sync CLI for Kubernetes deployments
- Secure PUT /properties endpoint with Bearer token auth

Admin GraphQL API:
- Entity CRUD via Seaography at /admin/graphql
- Custom job management API at /admin/jobs
- Mutations: triggerJob
- Queries: jobLogs, availableJobs
- GraphiQL playgrounds for both endpoints

Background Jobs:
- tokio-cron-scheduler integration
- Automated cleanup of expired sessions (hourly)
- Automated cleanup of expired refresh tokens (hourly)
- Job execution tracking in database
- Manual job triggering via GraphQL

User Sync CLI:
- Command: barycenter sync-users --file users.json
- Idempotent user synchronization from JSON
- Creates new users with hashed passwords
- Updates existing users (enabled, email_verified, email)
- Syncs custom properties per user
- Perfect for Kubernetes init containers

Security Enhancements:
- PUT /properties endpoint requires Bearer token
- Users can only modify their own properties
- Public registration disabled by default
- Admin API on separate port for network isolation

Database:
- New job_executions table for job tracking
- User update functions (update_user, update_user_email)
- PostgreSQL + SQLite support maintained

Configuration:
- allow_public_registration setting (default: false)
- admin_port setting (default: main port + 1)

Documentation:
- Comprehensive Kubernetes deployment guide
- User sync JSON schema and examples
- Init container and CronJob examples
- Production deployment patterns

Files Added:
- src/admin_graphql.rs - GraphQL schema builders
- src/admin_mutations.rs - Custom mutations and queries
- src/jobs.rs - Job scheduler and tracking
- src/user_sync.rs - User sync logic
- src/entities/ - SeaORM entities (8 entities)
- docs/kubernetes-deployment.md - K8s deployment guide
- users.json.example - User sync example

Dependencies:
- tokio-cron-scheduler 0.13
- seaography 1.1.4
- async-graphql 7.0
- async-graphql-axum 7.0

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:06:50 +01:00

22 KiB

Kubernetes Deployment Guide

This guide covers deploying Barycenter in Kubernetes with user sync, persistent storage, and proper configuration management.

Table of Contents

Quick Start

# Create namespace
kubectl create namespace barycenter

# Apply all manifests
kubectl apply -f deploy/kubernetes/

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                     Kubernetes Cluster                   │
│                                                          │
│  ┌────────────────────────────────────────────────────┐ │
│  │              Barycenter Deployment                  │ │
│  │                                                     │ │
│  │  ┌─────────────────┐    ┌──────────────────────┐  │ │
│  │  │  Init Container │───▶│   Main Container     │  │ │
│  │  │  (User Sync)    │    │   (OIDC Server)      │  │ │
│  │  └─────────────────┘    └──────────────────────┘  │ │
│  │          │                        │                │ │
│  │          └────────────────────────┘                │ │
│  │                     │                              │ │
│  │                     ▼                              │ │
│  │          ┌────────────────────┐                    │ │
│  │          │ PersistentVolume   │                    │ │
│  │          │ (SQLite/Data)      │                    │ │
│  │          └────────────────────┘                    │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                          │
│  ┌────────────────┐  ┌────────────────┐                 │
│  │   ConfigMap    │  │     Secret     │                 │
│  │ (config.toml)  │  │  (users.json)  │                 │
│  └────────────────┘  └────────────────┘                 │
│                                                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │                   Services                       │   │
│  │  ┌──────────────┐        ┌──────────────────┐  │   │
│  │  │   Public     │        │      Admin       │  │   │
│  │  │  (Port 8080) │        │   (Port 8081)    │  │   │
│  │  └──────────────┘        └──────────────────┘  │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Prerequisites

  • Kubernetes cluster (1.20+)
  • kubectl configured
  • Container registry access (or Docker Hub)
  • Persistent storage provisioner (optional, for production)

Configuration

ConfigMap for Application Configuration

Create barycenter-config.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: barycenter-config
  namespace: barycenter
data:
  config.toml: |
    [server]
    host = "0.0.0.0"
    port = 8080
    admin_port = 8081
    # IMPORTANT: Set this to your actual domain in production
    public_base_url = "https://auth.example.com"
    allow_public_registration = false

    [database]
    # SQLite for simplicity (use PostgreSQL in production)
    url = "sqlite:///data/barycenter.db?mode=rwc"

    [keys]
    jwks_path = "/data/jwks.json"
    private_key_path = "/data/private_key.pem"
    alg = "RS256"

    [federation]
    trust_anchors = []

Secret for User Data

Create barycenter-users-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: barycenter-users
  namespace: barycenter
type: Opaque
stringData:
  users.json: |
    {
      "users": [
        {
          "username": "admin",
          "email": "admin@example.com",
          "password": "CHANGE-ME-IN-PRODUCTION",
          "enabled": true,
          "email_verified": true,
          "properties": {
            "role": "administrator",
            "display_name": "System Administrator"
          }
        },
        {
          "username": "app-service",
          "email": "service@example.com",
          "password": "service-account-password",
          "enabled": true,
          "email_verified": true,
          "properties": {
            "role": "service_account",
            "display_name": "Application Service Account"
          }
        }
      ]
    }

IMPORTANT: In production, generate this secret from a secure source:

# Generate from template with environment variables
cat users.json.template | envsubst | kubectl create secret generic barycenter-users \
  --namespace=barycenter \
  --from-file=users.json=/dev/stdin \
  --dry-run=client -o yaml | kubectl apply -f -

Storage

Development: EmptyDir (Data lost on pod restart)

volumes:
- name: data
  emptyDir: {}

Production: PersistentVolumeClaim

Create barycenter-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: barycenter-data
  namespace: barycenter
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  # Optional: Use a specific storage class
  # storageClassName: fast-ssd

User Management

Init Container for User Sync

The init container runs before the main application starts and syncs users from the secret:

initContainers:
- name: user-sync
  image: your-registry/barycenter:latest
  command:
    - barycenter
    - sync-users
    - --file
    - /secrets/users.json
  volumeMounts:
  - name: users-secret
    mountPath: /secrets
    readOnly: true
  - name: data
    mountPath: /data
  - name: config
    mountPath: /app
    readOnly: true
  env:
  - name: RUST_LOG
    value: info

Standalone User Sync Job

For updating users without redeploying:

Create barycenter-user-sync-job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: barycenter-user-sync
  namespace: barycenter
spec:
  backoffLimit: 3
  template:
    metadata:
      labels:
        app: barycenter
        component: user-sync
    spec:
      restartPolicy: OnFailure
      containers:
      - name: user-sync
        image: your-registry/barycenter:latest
        command:
          - barycenter
          - sync-users
          - --file
          - /secrets/users.json
        volumeMounts:
        - name: users-secret
          mountPath: /secrets
          readOnly: true
        - name: data
          mountPath: /data
        - name: config
          mountPath: /app
          readOnly: true
        env:
        - name: RUST_LOG
          value: info
      volumes:
      - name: users-secret
        secret:
          secretName: barycenter-users
      - name: data
        persistentVolumeClaim:
          claimName: barycenter-data
      - name: config
        configMap:
          name: barycenter-config

Run with:

kubectl apply -f barycenter-user-sync-job.yaml

# Watch progress
kubectl logs -f job/barycenter-user-sync -n barycenter

# Clean up job after completion
kubectl delete job barycenter-user-sync -n barycenter

CronJob for Periodic User Sync

Create barycenter-user-sync-cronjob.yaml:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: barycenter-user-sync
  namespace: barycenter
spec:
  # Run every hour
  schedule: "0 * * * *"
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        metadata:
          labels:
            app: barycenter
            component: user-sync
        spec:
          restartPolicy: OnFailure
          containers:
          - name: user-sync
            image: your-registry/barycenter:latest
            command:
              - barycenter
              - sync-users
              - --file
              - /secrets/users.json
            volumeMounts:
            - name: users-secret
              mountPath: /secrets
              readOnly: true
            - name: data
              mountPath: /data
            - name: config
              mountPath: /app
              readOnly: true
            resources:
              requests:
                memory: "128Mi"
                cpu: "100m"
              limits:
                memory: "256Mi"
                cpu: "200m"
          volumes:
          - name: users-secret
            secret:
              secretName: barycenter-users
          - name: data
            persistentVolumeClaim:
              claimName: barycenter-data
          - name: config
            configMap:
              name: barycenter-config

Deployment

Main Deployment

Create barycenter-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: barycenter
  namespace: barycenter
  labels:
    app: barycenter
spec:
  replicas: 1  # NOTE: SQLite supports only 1 replica. Use PostgreSQL for HA.
  selector:
    matchLabels:
      app: barycenter
  template:
    metadata:
      labels:
        app: barycenter
    spec:
      # Init container syncs users before app starts
      initContainers:
      - name: user-sync
        image: your-registry/barycenter:latest
        command:
          - barycenter
          - sync-users
          - --file
          - /secrets/users.json
        volumeMounts:
        - name: users-secret
          mountPath: /secrets
          readOnly: true
        - name: data
          mountPath: /data
        - name: config
          mountPath: /app
          readOnly: true
        env:
        - name: RUST_LOG
          value: info
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"

      # Main application container
      containers:
      - name: barycenter
        image: your-registry/barycenter:latest
        ports:
        - name: public
          containerPort: 8080
          protocol: TCP
        - name: admin
          containerPort: 8081
          protocol: TCP
        volumeMounts:
        - name: data
          mountPath: /data
        - name: config
          mountPath: /app
          readOnly: true
        env:
        - name: RUST_LOG
          value: info
        # Liveness probe - checks if app is alive
        livenessProbe:
          httpGet:
            path: /.well-known/openid-configuration
            port: public
          initialDelaySeconds: 10
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3
        # Readiness probe - checks if app is ready to serve traffic
        readinessProbe:
          httpGet:
            path: /.well-known/openid-configuration
            port: public
          initialDelaySeconds: 5
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        resources:
          requests:
            memory: "256Mi"
            cpu: "200m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          readOnlyRootFilesystem: false
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL

      volumes:
      - name: users-secret
        secret:
          secretName: barycenter-users
      - name: data
        persistentVolumeClaim:
          claimName: barycenter-data
      - name: config
        configMap:
          name: barycenter-config

      # Security context for the pod
      securityContext:
        fsGroup: 1000

Services

Public Service (OIDC Endpoints)

Create barycenter-service-public.yaml:

apiVersion: v1
kind: Service
metadata:
  name: barycenter-public
  namespace: barycenter
  labels:
    app: barycenter
    component: public
spec:
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: public
    protocol: TCP
    name: http
  selector:
    app: barycenter

Admin Service (GraphQL API)

Create barycenter-service-admin.yaml:

apiVersion: v1
kind: Service
metadata:
  name: barycenter-admin
  namespace: barycenter
  labels:
    app: barycenter
    component: admin
spec:
  type: ClusterIP
  ports:
  - port: 8081
    targetPort: admin
    protocol: TCP
    name: http
  selector:
    app: barycenter

Ingress (Optional)

Create barycenter-ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: barycenter
  namespace: barycenter
  annotations:
    # cert-manager for TLS
    cert-manager.io/cluster-issuer: letsencrypt-prod
    # nginx ingress specific
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - auth.example.com
    secretName: barycenter-tls
  rules:
  # Public OIDC endpoints
  - host: auth.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: barycenter-public
            port:
              number: 8080
---
# Separate ingress for admin (restrict access)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: barycenter-admin
  namespace: barycenter
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    # Restrict to internal IPs only
    nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - admin.auth.example.com
    secretName: barycenter-admin-tls
  rules:
  - host: admin.auth.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: barycenter-admin
            port:
              number: 8081

Complete Example

Create a directory structure:

deploy/kubernetes/
├── namespace.yaml
├── configmap.yaml
├── secret.yaml
├── pvc.yaml
├── deployment.yaml
├── service-public.yaml
├── service-admin.yaml
└── ingress.yaml

namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: barycenter
  labels:
    name: barycenter

Apply All Resources

# Create namespace first
kubectl apply -f deploy/kubernetes/namespace.yaml

# Apply configuration and storage
kubectl apply -f deploy/kubernetes/configmap.yaml
kubectl apply -f deploy/kubernetes/secret.yaml
kubectl apply -f deploy/kubernetes/pvc.yaml

# Deploy application
kubectl apply -f deploy/kubernetes/deployment.yaml

# Expose services
kubectl apply -f deploy/kubernetes/service-public.yaml
kubectl apply -f deploy/kubernetes/service-admin.yaml

# Optional: Create ingress
kubectl apply -f deploy/kubernetes/ingress.yaml

Verify Deployment

# Check all resources
kubectl get all -n barycenter

# Check init container logs (user sync)
kubectl logs -n barycenter deployment/barycenter -c user-sync

# Check main container logs
kubectl logs -n barycenter deployment/barycenter -c barycenter -f

# Check services
kubectl get svc -n barycenter

# Port forward for testing
kubectl port-forward -n barycenter svc/barycenter-public 8080:8080
kubectl port-forward -n barycenter svc/barycenter-admin 8081:8081

# Test OIDC discovery
curl http://localhost:8080/.well-known/openid-configuration

# Test admin GraphQL
curl http://localhost:8081/admin/playground

Production Considerations

High Availability

SQLite Limitation:

  • SQLite only supports single writer
  • For HA, use PostgreSQL instead

PostgreSQL Setup:

  1. Update configmap.yaml:
[database]
url = "postgresql://barycenter:password@postgres-service:5432/barycenter"
  1. Deploy PostgreSQL (or use cloud provider):
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: barycenter
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: barycenter
        - name: POSTGRES_USER
          value: barycenter
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 20Gi
  1. Scale deployment:
spec:
  replicas: 3  # Now safe with PostgreSQL

Security Hardening

  1. Network Policies:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: barycenter-network-policy
  namespace: barycenter
spec:
  podSelector:
    matchLabels:
      app: barycenter
  policyTypes:
  - Ingress
  - Egress
  ingress:
  # Allow from ingress controller
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  # Admin access only from internal
  - from:
    - podSelector:
        matchLabels:
          role: admin
    ports:
    - protocol: TCP
      port: 8081
  egress:
  # Allow DNS
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53
  # Allow PostgreSQL
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  1. Pod Security Standards:
apiVersion: v1
kind: Namespace
metadata:
  name: barycenter
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
  1. Resource Quotas:
apiVersion: v1
kind: ResourceQuota
metadata:
  name: barycenter-quota
  namespace: barycenter
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 4Gi
    limits.cpu: "4"
    limits.memory: 8Gi
    persistentvolumeclaims: "5"

Monitoring

  1. ServiceMonitor (Prometheus Operator):
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: barycenter
  namespace: barycenter
spec:
  selector:
    matchLabels:
      app: barycenter
  endpoints:
  - port: http
    interval: 30s
    path: /metrics  # Add metrics endpoint to Barycenter
  1. Logging:
# Add to deployment
env:
- name: RUST_LOG
  value: "info,barycenter=debug"

Backup

apiVersion: batch/v1
kind: CronJob
metadata:
  name: barycenter-backup
  namespace: barycenter
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: alpine:latest
            command:
            - sh
            - -c
            - |
              apk add --no-cache sqlite
              sqlite3 /data/barycenter.db ".backup /backup/barycenter-$(date +%Y%m%d-%H%M%S).db"
              # Upload to S3/GCS/etc
            volumeMounts:
            - name: data
              mountPath: /data
              readOnly: true
            - name: backup
              mountPath: /backup
          volumes:
          - name: data
            persistentVolumeClaim:
              claimName: barycenter-data
          - name: backup
            persistentVolumeClaim:
              claimName: barycenter-backup
          restartPolicy: OnFailure

Troubleshooting

Check Init Container

# View init container logs
kubectl logs -n barycenter deployment/barycenter -c user-sync

# Common issues:
# - Secret not found: Check kubectl get secret -n barycenter
# - Permission denied: Check fsGroup and securityContext
# - Database locked: Check if multiple pods are running with SQLite

Check Main Container

# View logs
kubectl logs -n barycenter deployment/barycenter -c barycenter -f

# Exec into pod
kubectl exec -it -n barycenter deployment/barycenter -- sh

# Check database
ls -la /data/

Update Users

# Method 1: Update secret and restart
kubectl delete secret barycenter-users -n barycenter
kubectl create secret generic barycenter-users \
  --from-file=users.json=./users.json \
  -n barycenter

kubectl rollout restart deployment/barycenter -n barycenter

# Method 2: Run sync job
kubectl apply -f barycenter-user-sync-job.yaml
kubectl logs -f job/barycenter-user-sync -n barycenter

Summary

You now have:

  • Complete Kubernetes deployment setup
  • User sync via init containers
  • Persistent storage configuration
  • Service exposure (public + admin)
  • Production-ready configurations
  • HA setup with PostgreSQL
  • Security hardening options
  • Monitoring and backup strategies

For the actual Helm chart deployment, see deploy/helm/barycenter/.