Skip to main content

Kubernetes Deployment

The Iotistica cloud platform ships as a single Helm chart (charts/iotistica) that deploys every service needed to run the platform: the Cloud API, ingestion pipeline, MQTT broker, Redis, and optional dashboard and Node-RED instances.

Services

ServiceImagePortPurpose
apiiotistic/api3002REST + MQTT subscription, auth, device state
ingestioniotistic/ingestion3003Redis Streams consumer → TimescaleDB writer
mosquittoiegomez/mosquitto-go-auth1883 / 8883 / 9001MQTT broker with HTTP auth callback
redisredis:7-alpine6379Streams, pub/sub, cache, dedup
dashboardiotistic/dashboard80Web UI
nodered (optional)iotistic/noderedconfigurableLow-code automation

Networking is handled by Envoy Gateway via HTTPRoute (HTTP) and TLSRoute (MQTT). TLS certificates are issued by cert-manager.


Prerequisites

  • Kubernetes 1.27+
  • Helm 3
  • Envoy Gateway installed in the cluster
  • cert-manager with a letsencrypt-production ClusterIssuer
  • TimescaleDB (external CNPG cluster or managed service)
  • 1Password Operator (optional — required for managed secret injection)
  • Prometheus + ServiceMonitor CRD (optional — required for metrics scraping)

Quick Start

Lint and preview

helm lint charts/iotistica

helm template client-demo charts/iotistica \
-f charts/iotistica/values/demo/values.yaml \
--debug

Install (demo)

helm upgrade --install client-demo charts/iotistica \
-n demo --create-namespace \
-f charts/iotistica/values/demo/values.yaml

Install (production — standard tier)

helm upgrade --install iotistica charts/iotistica \
-n iotistica --create-namespace \
-f charts/iotistica/values/demo/values.yaml \
-f charts/iotistica/values/tiers/standard.yaml

Tier overlays are stacked on top of the base values — the rightmost -f wins for any key that appears in both files.


Configuration Tiers

Three tier overlays are provided under values/tiers/. Pick one based on fleet size.

Demo / Trial

Default values. Single replica, minimal resources. Suitable for development and evaluation.

ResourceCPU requestMemory requestCPU limitMemory limit
API100m256Mi500m1Gi
Ingestion300m256Mi1000m1Gi
Redis100m1Gi250m2Gi

Starter — up to 10 agents, ~200 r/s

-f charts/iotistica/values/tiers/starter.yaml
SettingValue
API CPU request250m
API memory request256Mi
API workers6
Redis max memory512 MB
Ingestion workers6
Raw readings retention30 days

Standard — up to 50 agents, ~2,000 r/s

-f charts/iotistica/values/tiers/standard.yaml
SettingValue
API replicas2
API CPU request1000m
API memory request512Mi
API HPAenabled (min 2, max 4)
Redis max memory2 GB
Ingestion replicas2, workers 10
Ingestion HPAenabled (min 2, max 8)
Stream buffer (REDIS_INGESTION_STREAM_MAXLEN)600,000 messages
Raw readings retention60 days

Enterprise — up to 200 agents, ~50,000 r/s

-f charts/iotistica/values/tiers/enterprise.yaml
SettingValue
API replicas4
API CPU request2000m
API memory limit4Gi
API HPAenabled (min 4, max 10), 65% CPU target
Redis clusterrequired — set redis.cluster: true
Redis max memory8 GB
Ingestion replicas4, workers 20
Ingestion HPAenabled (min 4, max 20)
Stream buffer1,200,000 messages
Raw readings retention90 days
Disk spool max5 GB

Services Reference

Cloud API (api)

The API service does two things simultaneously:

  1. Maintains a persistent MQTT subscription to the broker and routes incoming agent messages (telemetry, state, events, jobs) to Redis Streams and TimescaleDB.
  2. Serves the REST API for the dashboard and agent provisioning.

Key environment variables (set via api.env in values or per-tier override):

VariableDefaultNotes
PORT3002
DB_POOL_SIZE20Increase for enterprise
DB_STATEMENT_TIMEOUT_MS45000
MQTT_BROKER_URL(from secret)e.g. mqtt://mosquitto:1883
MQTT_AUTH_CACHE_TTL_SECONDS300Auth callback cache TTL
MQTT_PERSIST_TO_DBtruePersist MQTT messages to DB
JWT_ACCESS_TOKEN_EXPIRY15m
JWT_REFRESH_TOKEN_EXPIRY7d
DISK_SPOOL_ENABLEDtrueLocal buffer on broker loss
DISK_SPOOL_MAX_SIZE_MB1000
AI_PROVIDERopenaiUsed for anomaly summaries
CORS_ORIGINShttps://demo.iotistica.com,...Comma-separated allowed origins

Probes:

  • startupProbe: checks /health every 5 s, up to 36 failures (3-minute startup budget)
  • livenessProbe: every 10 s
  • readinessProbe: every 5 s

Rate limiting: Envoy Gateway BackendTrafficPolicy limits the API to 1,000 requests per minute.

HPA (standard / enterprise):

SettingStandardEnterprise
Min replicas24
Max replicas410
CPU target70%65%
Memory target80%75%
Scale-up stabilization60 s60 s
Scale-down stabilization5 min5 min

Ingestion (ingestion)

The ingestion service is a stateless Redis Streams consumer. It reads from tenant:{id}:agent:devices:ingestion, batches records, and writes to TimescaleDB.

Key environment variables:

VariableDefaultNotes
INGESTION_PROFILEbatchbatch / balanced / streaming
WORKER_COUNTper tierParallel write workers per pod
BATCH_SIZEper tierRows per INSERT
AUTOSCALE_MIN_WORKERSper tierInternal worker autoscale lower bound
AUTOSCALE_MAX_WORKERSper tierInternal worker autoscale upper bound
AUTOSCALE_LAG_SCALE_UP_MS5000Scale up when stream lag exceeds this
AUTOSCALE_LAG_CRITICAL_MS15000Critical lag threshold
REDIS_INGESTION_STREAM_MAXLENper tierMax messages retained in stream
DISK_SPOOL_ENABLEDtrueFallback if Redis or DB is unreachable
DISK_SPOOL_PATH/var/lib/iotistic/spoolMounted PVC if persistence enabled

Ingestion profiles:

ProfileBehaviour
batchOptimized for throughput — larger batches, higher latency
balancedDefault balance of latency and throughput
streamingLow-latency, smaller batches, higher DB load

Spool persistence: If ingestion.spoolPersistence.enabled: true, a PVC is mounted at /var/lib/iotistic/spool. The deployment strategy switches to Recreate when a ReadWriteOnce PVC is used.

Stream key resolver job: An ArgoCD-hook Job runs on every sync, extracts the tenantId from the license JWT, and writes it into a runtime Secret ({release}-ingestion-runtime-env):

INGESTION_TENANT_ID=<uuid>
REDIS_INGESTION_STREAM_KEY=tenant:<uuid>:agent:devices:ingestion

Mosquitto MQTT Broker (mosquitto)

Uses iegomez/mosquitto-go-auth, which adds an HTTP authentication backend plugin to Mosquitto.

ListenerPortProtocol
Plain MQTT1883TCP
TLS MQTT8883TLS (cert-manager certificate)
WebSocket9001WS

Authentication is an HTTP callback to the Cloud API:

POST /mosquitto-auth/user — credential validation
POST /mosquitto-auth/superuser — superuser check
POST /mosquitto-auth/acl — topic ACL check

Agents authenticate using their API key as the MQTT username and a provisioned password. Anonymous connections are rejected.

TLS is issued by cert-manager via the letsencrypt-production ClusterIssuer. Configure the hostname via mosquitto.tls.commonName.


Redis (redis)

Single-instance Redis (or cluster for enterprise). Used for:

  • tenant:{id}:agent:devices:ingestion — ingestion stream
  • tenant:{id}:* — device state, pub/sub channels, dedup keys (24 hr TTL)
SettingDemoStarterStandardEnterprise
Max memory1,536 MB512 MB2 GB8 GB
Max memory policynoevictionnoevictionnoevictionnoeviction
RDB / AOFoffoffoffoff
Clusterfalsefalsefalsetrue (recommended)

:::caution No persistence Redis is intentionally configured without RDB snapshots or AOF. All durable state lives in TimescaleDB. On Redis restart, the ingestion stream and dedup keys are lost — this is expected and acceptable. :::


Dashboard (dashboard)

A static Vue 3 SPA served by Nginx. Runtime configuration is injected via a ConfigMap mounted as config.js:

window.env = {
VITE_API_URL: 'https://demo-api.iotistica.com',
VITE_AUTH0_DOMAIN: 'auth.iotistica.com',
VITE_AUTH0_CLIENT_ID: '...',
VITE_AUTH0_AUDIENCE: 'https://api.iotistica.com',
VITE_AUTH0_CALLBACK_URL: 'https://demo.iotistica.com/auth/callback',
VITE_PROVISIONING_API_URL: 'https://api.iotistica.com/provisioning',
APP_VERSION: 'v0.0.22-rc.1'
}

Update dashboard.config.* in values to override these per environment.


Required Secrets

The chart expects these Kubernetes Secrets to exist in the target namespace before install. If using the 1Password Operator, OnePasswordItem resources create them automatically from vault entries.

Secret nameKeysUsed by
sql-credentials-{namespace}server, port, dbname, username, passwordAPI, Ingestion
mqtt-credentials-{namespace}username, passwordAPI (MQTT connection to broker)
openai-credentials-masterkeyAPI (AI / anomaly summaries)
api-jwt-{namespace}tokenAPI (JWT signing secret)
api-license-credentials-{namespace}keyAPI, Ingestion
api-license-public-key-masterkeyAPI
api-bootstrap-token-mastertokenAPI
internal-auth-token-mastertokenAPI

For Redis auth (redis.auth.enabled: true), the chart creates {release}-redis-auth automatically.


Database Initialization

An optional Job (db-init) creates the application role and database, grants permissions, and enables the TimescaleDB extension. It is disabled for demo environments where the role is created manually before GitOps.

dbInit:
enabled: true
adminSecretName: pg-admin-credentials

The job connects as the admin user and:

  1. Creates or alters the application role
  2. Creates or alters the database
  3. Grants CONNECT, schema USAGE/CREATE, and table/sequence permissions
  4. Runs CREATE EXTENSION IF NOT EXISTS timescaledb

TimescaleDB Lifecycle Policies

A timescaledb-policy ArgoCD-hook Job (wave "1") runs on every sync and idempotently applies compression and retention policies.

Table / ViewCompress afterRetain
readings (raw hypertable)2 days (demo)30d / 60d / 90d by tier
readings_1m (continuous aggregate)7 days90 days
agent_metrics_5min60 days (enterprise)

Fleet Namespaces

When fleetNamespaces.enabled: true, the chart provisions isolated Kubernetes namespaces for agent fleets:

fleet-{release-namespace}-{name}

Each namespace receives a ResourceQuota, LimitRange, RoleBinding, and optional NetworkPolicy.

Default resource quota per agent slot:

ResourcePer-agent allocation
CPU request300m
Memory request480Mi
CPU limitup to 2000m shared
Memory limitup to 4Gi shared

Monitoring

ServiceMonitor resources are created for the API and Ingestion services when monitoring.enabled: true:

TargetPathInterval
{release}-api:3002/metrics30s
{release}-ingestion:3003/metrics30s

Operational Checks

# List all pods
kubectl get pods -n demo

# API logs
kubectl logs -n demo -l app=iotistic-api --tail=100 -f

# Check ingestion stream depth (consumer lag)
kubectl exec -n demo deploy/client-demo-redis -- \
redis-cli XLEN tenant:<uuid>:agent:devices:ingestion

# Verify Mosquitto is accepting connections
kubectl exec -n demo deploy/client-demo-mosquitto -- \
mosquitto_sub -h localhost -p 1883 -t '$SYS/#' -C 1