Kubernetes

Kubernetes provides the most flexible deployment model for Zentinel, supporting multiple patterns from simple sidecar deployments to sophisticated service mesh integrations.

Deployment Patterns

PatternDescriptionBest For
SidecarAgents in same pod as ZentinelSimple setups, low latency
ServiceAgents as separate deploymentsShared agents, independent scaling
DaemonSetZentinel on every nodeEdge/gateway deployments

Pattern 1: Sidecar Deployment

Agents run as sidecar containers in the same pod as Zentinel.

# zentinel-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: zentinel
  labels:
    app: zentinel
spec:
  replicas: 3
  selector:
    matchLabels:
      app: zentinel
  template:
    metadata:
      labels:
        app: zentinel
    spec:
      containers:
        # ─────────────────────────────────────────────────
        # Zentinel Proxy
        # ─────────────────────────────────────────────────
        - name: zentinel
          image: ghcr.io/zentinelproxy/zentinel:latest
          ports:
            - name: http
              containerPort: 8080
            - name: admin
              containerPort: 9090
          volumeMounts:
            - name: config
              mountPath: /etc/zentinel
              readOnly: true
            - name: sockets
              mountPath: /var/run/zentinel
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "1000m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: admin
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: admin
            initialDelaySeconds: 5
            periodSeconds: 5

        # ─────────────────────────────────────────────────
        # Auth Agent (sidecar)
        # ─────────────────────────────────────────────────
        - name: auth-agent
          image: ghcr.io/zentinelproxy/zentinel-auth:latest
          args:
            - "--socket"
            - "/var/run/zentinel/auth.sock"
          volumeMounts:
            - name: sockets
              mountPath: /var/run/zentinel
            - name: auth-secrets
              mountPath: /etc/auth/secrets
              readOnly: true
          env:
            - name: AUTH_SECRET
              valueFrom:
                secretKeyRef:
                  name: zentinel-secrets
                  key: auth-secret
          resources:
            requests:
              cpu: "50m"
              memory: "64Mi"
            limits:
              cpu: "200m"
              memory: "128Mi"

        # ─────────────────────────────────────────────────
        # WAF Agent (sidecar, gRPC)
        # ─────────────────────────────────────────────────
        - name: waf-agent
          image: ghcr.io/zentinelproxy/zentinel-waf:latest
          args:
            - "--grpc"
            - "127.0.0.1:50051"
          resources:
            requests:
              cpu: "100m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            grpc:
              port: 50051
            initialDelaySeconds: 5
            periodSeconds: 10

      volumes:
        - name: config
          configMap:
            name: zentinel-config
        - name: sockets
          emptyDir: {}
        - name: auth-secrets
          secret:
            secretName: zentinel-secrets
---
apiVersion: v1
kind: Service
metadata:
  name: zentinel
spec:
  selector:
    app: zentinel
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: admin
      port: 9090
      targetPort: 9090
  type: LoadBalancer

ConfigMap

# zentinel-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: zentinel-config
data:
  zentinel.kdl: |
    server {
        listen "0.0.0.0:8080"
    }

    admin {
        listen "0.0.0.0:9090"
    }

    agents {
        agent "auth" type="auth" {
            unix-socket "/var/run/zentinel/auth.sock"
            events "request_headers"
            timeout-ms 50
            failure-mode "closed"
        }

        agent "waf" type="waf" {
            grpc "http://127.0.0.1:50051"
            events "request_headers" "request_body"
            timeout-ms 100
            failure-mode "open"
        }
    }

    upstreams {
        upstream "api" {
            target "api-service.default.svc.cluster.local:80"
        }
    }

    routes {
        route "api" {
            matches { path-prefix "/api/" }
            upstream "api"
            agents "auth" "waf"
        }
    }

Secrets

# zentinel-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: zentinel-secrets
type: Opaque
data:
  auth-secret: <base64-encoded-secret>

Pattern 2: Separate Service

Agents run as independent deployments, accessed via Kubernetes services.

# waf-agent-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: waf-agent
  labels:
    app: waf-agent
spec:
  replicas: 3
  selector:
    matchLabels:
      app: waf-agent
  template:
    metadata:
      labels:
        app: waf-agent
    spec:
      containers:
        - name: waf-agent
          image: ghcr.io/zentinelproxy/zentinel-waf:latest
          args:
            - "--grpc"
            - "0.0.0.0:50051"
          ports:
            - name: grpc
              containerPort: 50051
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1000m"
              memory: "1Gi"
          livenessProbe:
            grpc:
              port: 50051
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            grpc:
              port: 50051
            initialDelaySeconds: 5
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: waf-agent
spec:
  selector:
    app: waf-agent
  ports:
    - name: grpc
      port: 50051
      targetPort: 50051
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: waf-agent-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: waf-agent
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Update Zentinel config to use the service:

agent "waf" type="waf" {
    grpc "http://waf-agent.default.svc.cluster.local:50051"
    events "request_headers" "request_body"
    timeout-ms 200
    failure-mode "open"
}

Pattern 3: DaemonSet (Edge Gateway)

Run Zentinel on every node for edge/gateway scenarios.

# zentinel-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: zentinel-edge
  labels:
    app: zentinel-edge
spec:
  selector:
    matchLabels:
      app: zentinel-edge
  template:
    metadata:
      labels:
        app: zentinel-edge
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: zentinel
          image: ghcr.io/zentinelproxy/zentinel:latest
          ports:
            - name: http
              containerPort: 80
              hostPort: 80
            - name: https
              containerPort: 443
              hostPort: 443
          volumeMounts:
            - name: config
              mountPath: /etc/zentinel
          securityContext:
            capabilities:
              add:
                - NET_BIND_SERVICE
      volumes:
        - name: config
          configMap:
            name: zentinel-edge-config
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

Helm Chart

The official Helm chart provides the easiest way to deploy Zentinel on Kubernetes with production-ready defaults.

Repository: github.com/zentinelproxy/zentinel-helm

Installation

# Install from source
git clone https://github.com/zentinelproxy/zentinel-helm.git
cd zentinel-helm
helm install zentinel .

# Install with custom values
helm install zentinel . -f my-values.yaml

# Upgrade
helm upgrade zentinel . -f my-values.yaml

Quick Start

# values.yaml
replicaCount: 2

config:
  raw: |
    listeners {
        listener "http" {
            address "0.0.0.0:80"
            protocol "http"
        }
    }

    routes {
        route "api" {
            matches { path-prefix "/api" }
            upstream "backend"
        }
    }

    upstreams {
        upstream "backend" {
            target "my-service:8080"
            health-check { path "/health" }
        }
    }

Production Example

# production-values.yaml
replicaCount: 3

config:
  raw: |
    listeners {
        listener "https" {
            address "0.0.0.0:443"
            protocol "https"
            tls {
                cert "/etc/zentinel/certs/tls.crt"
                key "/etc/zentinel/certs/tls.key"
            }
        }
    }

    routes {
        route "api" {
            matches { path-prefix "/" }
            upstream "backend"
        }
    }

    upstreams {
        upstream "backend" {
            target "api-service.default.svc.cluster.local:80"
            load-balancing "round_robin"
            health-check {
                path "/health"
                interval-secs 10
            }
        }
    }

# Resources
resources:
  requests:
    cpu: "200m"
    memory: "256Mi"
  limits:
    cpu: "2000m"
    memory: "1Gi"

# Autoscaling
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilizationPercentage: 70

# High availability
podDisruptionBudget:
  enabled: true
  minAvailable: 2

# Prometheus monitoring
serviceMonitor:
  enabled: true
  interval: 15s

# Ingress
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: api-tls
      hosts:
        - api.example.com

# TLS certificates
extraVolumes:
  - name: tls-certs
    secret:
      secretName: zentinel-tls

extraVolumeMounts:
  - name: tls-certs
    mountPath: /etc/zentinel/certs
    readOnly: true

Using an Existing ConfigMap

If you manage configuration separately:

config:
  existingConfigMap: my-zentinel-config
  configKey: zentinel.kdl

Chart Features

FeatureDescription
Secure defaultsNon-root user (65534), read-only filesystem, no privilege escalation
ConfigMapInline KDL config or reference existing ConfigMap
HPAHorizontal Pod Autoscaler for automatic scaling
PDBPod Disruption Budget for high availability
ServiceMonitorPrometheus Operator integration
IngressOptional ingress with TLS support
Extra volumesMount TLS certs, secrets, etc.

All Configuration Options

See the values.yaml for all available options.

Service Mesh Integration

Istio

# zentinel-virtualservice.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: zentinel
spec:
  hosts:
    - zentinel
  http:
    - route:
        - destination:
            host: zentinel
            port:
              number: 8080
      timeout: 30s
      retries:
        attempts: 3
        perTryTimeout: 10s
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: zentinel
spec:
  host: zentinel
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: UPGRADE
    loadBalancer:
      simple: LEAST_CONN

Linkerd

# Add annotation to deployment
metadata:
  annotations:
    linkerd.io/inject: enabled

Observability

Prometheus ServiceMonitor

# zentinel-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: zentinel
  labels:
    app: zentinel
spec:
  selector:
    matchLabels:
      app: zentinel
  endpoints:
    - port: admin
      path: /metrics
      interval: 15s

Grafana Dashboard

# zentinel-dashboard-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: zentinel-dashboard
  labels:
    grafana_dashboard: "1"
data:
  zentinel.json: |
    {
      "title": "Zentinel Proxy",
      "panels": [
        {
          "title": "Request Rate",
          "targets": [
            {
              "expr": "rate(zentinel_requests_total[5m])"
            }
          ]
        }
      ]
    }

Logging with Fluentd

# fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluent.conf: |
    <source>
      @type tail
      path /var/log/containers/zentinel*.log
      pos_file /var/log/zentinel.pos
      tag zentinel.*
      <parse>
        @type json
      </parse>
    </source>

    <match zentinel.**>
      @type elasticsearch
      host elasticsearch
      port 9200
      index_name zentinel
    </match>

Network Policies

# zentinel-networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: zentinel
spec:
  podSelector:
    matchLabels:
      app: zentinel
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # Allow from ingress controller
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080
    # Allow admin from monitoring namespace
    - from:
        - namespaceSelector:
            matchLabels:
              name: monitoring
      ports:
        - protocol: TCP
          port: 9090
  egress:
    # Allow to upstream services
    - to:
        - namespaceSelector:
            matchLabels:
              name: backend
      ports:
        - protocol: TCP
          port: 80
    # Allow to agent services
    - to:
        - podSelector:
            matchLabels:
              app: waf-agent
      ports:
        - protocol: TCP
          port: 50051
    # Allow DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53

Rolling Updates

# Update strategy in deployment
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
# Trigger rolling update
kubectl set image deployment/zentinel zentinel=ghcr.io/zentinelproxy/zentinel:v1.2.0

# Watch rollout
kubectl rollout status deployment/zentinel

# Rollback if needed
kubectl rollout undo deployment/zentinel

Troubleshooting

Pod Not Starting

# Check pod status
kubectl get pods -l app=zentinel

# Describe pod
kubectl describe pod zentinel-xxx

# Check logs
kubectl logs zentinel-xxx -c zentinel
kubectl logs zentinel-xxx -c auth-agent

# Check events
kubectl get events --sort-by='.lastTimestamp'

Agent Connection Issues

# Check service discovery
kubectl exec zentinel-xxx -c zentinel -- nslookup waf-agent

# Test gRPC connection
kubectl exec zentinel-xxx -c zentinel -- grpcurl -plaintext waf-agent:50051 list

# Check socket exists (sidecar)
kubectl exec zentinel-xxx -c zentinel -- ls -la /var/run/zentinel/

Resource Issues

# Check resource usage
kubectl top pods -l app=zentinel

# Check resource limits
kubectl describe pod zentinel-xxx | grep -A5 Resources

# Check OOMKilled
kubectl get events | grep OOM

Config Issues

# Validate config
kubectl exec zentinel-xxx -c zentinel -- zentinel --config /etc/zentinel/zentinel.kdl --dry-run

# Check configmap
kubectl get configmap zentinel-config -o yaml