Skip to content

Kubernetes Deployment

Deploy k13d directly to your Kubernetes cluster for production use.

Quick Start

# Apply manifests
kubectl apply -f https://raw.githubusercontent.com/cloudbro-kube-ai/k13d/main/deploy/kubernetes/deployment.yaml

# Wait for pod
kubectl wait --for=condition=ready pod -l app=k13d

# Access via port-forward
kubectl port-forward svc/k13d 8080:8080

Access at: http://localhost:8080

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Kubernetes Cluster                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │   Ingress    │───▶│   Service    │───▶│    Pod       │       │
│  │   (optional) │    │   (k13d)     │    │   (k13d)     │       │
│  └──────────────┘    └──────────────┘    └──────────────┘       │
│                                                 │                │
│                                          ┌──────┴──────┐        │
│                                          │  ServiceAcc │        │
│                                          │  (k13d-sa)  │        │
│                                          └─────────────┘        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Manifests

Basic Deployment

# k13d-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k13d
  labels:
    app: k13d
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k13d
  template:
    metadata:
      labels:
        app: k13d
    spec:
      serviceAccountName: k13d
      containers:
        - name: k13d
          image: cloudbro/k13d:latest
          args: ["-web", "-port", "8080"]
          ports:
            - containerPort: 8080
          env:
            - name: OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: k13d-secrets
                  key: openai-api-key
          resources:
            requests:
              memory: "256Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /api/health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /api/health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: k13d
spec:
  selector:
    app: k13d
  ports:
    - port: 8080
      targetPort: 8080
  type: ClusterIP

RBAC Configuration

# k13d-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: k13d
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: k13d
rules:
  # Read access to all resources
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  # Write access (for AI operations)
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps", "secrets"]
    verbs: ["create", "update", "patch", "delete"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
    verbs: ["create", "update", "patch", "delete"]
  # Scale permissions
  - apiGroups: ["apps"]
    resources: ["deployments/scale", "statefulsets/scale", "replicasets/scale"]
    verbs: ["get", "update", "patch"]
  # Logs and exec
  - apiGroups: [""]
    resources: ["pods/log", "pods/exec"]
    verbs: ["get", "create"]
  # Metrics
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: k13d
subjects:
  - kind: ServiceAccount
    name: k13d
    namespace: default
roleRef:
  kind: ClusterRole
  name: k13d
  apiGroup: rbac.authorization.k8s.io

Secrets

# k13d-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: k13d-secrets
type: Opaque
stringData:
  openai-api-key: "sk-your-key-here"
  password: "your-secure-password"

ConfigMap

# k13d-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: k13d-config
data:
  config.yaml: |
    llm:
      provider: openai
      model: gpt-4

    auth:
      password: ${K13D_PASSWORD}

    enable_audit: true
    language: en

Ingress

# k13d-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: k13d
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - k13d.example.com
      secretName: k13d-tls
  rules:
    - host: k13d.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: k13d
                port:
                  number: 8080

Installation

Apply All Manifests

# Create namespace
kubectl create namespace k13d

# Apply secrets first
kubectl apply -f k13d-secrets.yaml -n k13d

# Apply RBAC
kubectl apply -f k13d-rbac.yaml -n k13d

# Apply config
kubectl apply -f k13d-configmap.yaml -n k13d

# Apply deployment
kubectl apply -f k13d-deployment.yaml -n k13d

# Apply ingress (optional)
kubectl apply -f k13d-ingress.yaml -n k13d

Verify Installation

# Check pod status
kubectl get pods -n k13d

# Check logs
kubectl logs -f deployment/k13d -n k13d

# Test health
kubectl exec -it deployment/k13d -n k13d -- curl localhost:8080/api/health

Access Methods

Port Forward

kubectl port-forward svc/k13d 8080:8080 -n k13d

NodePort

apiVersion: v1
kind: Service
metadata:
  name: k13d
spec:
  type: NodePort
  selector:
    app: k13d
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30080

Access: http://<node-ip>:30080

LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: k13d
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: k13d
  ports:
    - port: 80
      targetPort: 8080

Advanced Configuration

With Ollama Sidecar

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k13d
spec:
  template:
    spec:
      containers:
        - name: k13d
          image: cloudbro/k13d:latest
          args: ["-web", "-port", "8080"]
          env:
            - name: LLM_PROVIDER
              value: "ollama"
            - name: LLM_ENDPOINT
              value: "http://localhost:11434"

        - name: ollama
          image: ollama/ollama:latest
          ports:
            - containerPort: 11434
          volumeMounts:
            - name: ollama-models
              mountPath: /root/.ollama
          # Init container to pull model
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "sleep 10 && ollama pull llama3.2"]

      volumes:
        - name: ollama-models
          persistentVolumeClaim:
            claimName: ollama-models-pvc

High Availability

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k13d
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: k13d
                topologyKey: kubernetes.io/hostname

Resource Limits

resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

Monitoring

Prometheus Metrics

apiVersion: v1
kind: Service
metadata:
  name: k13d
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "8080"
    prometheus.io/path: "/metrics"

Grafana Dashboard

Import dashboard ID: XXXXX (k13d dashboard)

Security Hardening

Pod Security

spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: k13d
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL

Network Policy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: k13d
spec:
  podSelector:
    matchLabels:
      app: k13d
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
      ports:
        - protocol: TCP
          port: 443  # HTTPS to API
        - protocol: TCP
          port: 6443 # Kubernetes API

Read-Only RBAC

For read-only access:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: k13d-readonly
rules:
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps", "batch", "extensions"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]

Troubleshooting

Pod CrashLoopBackOff

# Check logs
kubectl logs deployment/k13d -n k13d --previous

# Common issues:
# - Missing secrets
# - Invalid RBAC
# - Network issues

Permission Denied

# Verify service account
kubectl auth can-i get pods --as=system:serviceaccount:k13d:k13d

# Check RBAC
kubectl describe clusterrolebinding k13d

Cannot Access API

# Check service
kubectl get svc k13d -n k13d

# Check endpoints
kubectl get endpoints k13d -n k13d

# Test from cluster
kubectl run test --rm -it --image=curlimages/curl -- curl k13d.k13d:8080/api/health

Next Steps