Ingress Controller for Thingshub Deployments (Traefik)
Starting from version 8.0, the thingsHub application allows Traefik v3 as its ingress controller for routing external traffic to the internal Kubernetes services. Traefik is deployed using the official Traefik Helm chart.
This guide provides step-by-step instructions for deploying and configuring a production-ready Traefik ingress controller.
Prerequisites
Before deploying the Traefik ingress controller, ensure the following requirements are met:
Kubernetes Cluster: A running Kubernetes cluster (GKE, EKS, or RKE).
Helm: Helm v3.10+ must be installed. (Install Helm)
kubectl:
kubectlmust be installed and configured to connect to the target cluster.External IP Address: A static external IP address is required for the ingress load balancer. This IP will be the public entry point for all traffic to thingsHub tenants.
Google Cloud (GKE): Reserve a regional static IP:
CODEgcloud compute addresses create traefik-ingress-ip --region <YOUR_REGION>AWS (EKS): Allocate an Elastic IP via the AWS Console or CLI.
Heads-up notice
The Traefik configuration is split into two logical files:
traefik-values.yaml— The Shared Production Core. This configuration contains the recommended settings for high availability, resource allocation, Traefik providers, observability, and logging. These settings should remain the same across all deployments.cloud-environment-config.yaml— The Cloud-Specific Networking layer. Operators should use this file as a reference and adapt it for their own GitOps pipeline and specific cloud environment.
⚠️ IMPORTANT: Do not copy and paste the example configurations directly. Use them as a reference for your own GitOps or Helm value overrides.
Example configuration files
Shared Production Core (traefik-values.yaml)
This file contains the recommended base configuration. These settings should remain the same.
# ==========================================
# 1. SHARED PRODUCTION CORE (STAY THE SAME)
# ==========================================
additionalArguments:
- "--global.checknewversion=false"
- "--global.sendanonymoususage=false"
# High Availability Configuration
podDisruptionBudget:
enabled: true
minAvailable: 1
# Deployment Configuration
deployment:
replicas: 3 # Minimum 2 for high availability
affinity:
# Pod Anti-Affinity to ensure pods stay on different nodes
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: '{{ template "traefik.name" . }}'
app.kubernetes.io/instance: '{{ .Release.Name }}-{{ include "traefik.namespace" . }}'
topologyKey: kubernetes.io/hostname
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "2Gi"
# Traefik Features Configuration
providers:
kubernetesIngress:
enabled: true
allowExternalNameServices: true
allowEmptyServices: true
publishedService:
enabled: true
kubernetesCRD:
enabled: true
allowExternalNameServices: true
allowCrossNamespace: true
# Experimental Features (Traefik v3)
experimental:
kubernetesGateway:
enabled: true
# ==========================================
# 2. OBSERVABILITY & SECURITY
# ==========================================
metrics:
prometheus:
enabled: true
addRoutersLabels: true
addEntryPointsLabels: true
service:
enabled: true
serviceMonitor:
enabled: false # Set to true if Prometheus Operator is present
# Logging Configuration
logs:
general:
level: WARN
access:
enabled: true
format: json
Cloud-Specific Networking (cloud-environment-config.yaml)
Operators must adapt this file for their cloud provider and include it alongside the shared core during helm install or helm upgrade.
# ==========================================
# CLOUD-SPECIFIC NETWORKING (LOCALIZE HERE)
# ==========================================
service:
enabled: true
type: LoadBalancer
# Common setting to ensure Source IP preservation
spec:
externalTrafficPolicy: Local
## --- OPTION A: GOOGLE CLOUD (GKE) ---
annotations:
cloud.google.com/l4-rbs: "enabled"
networking.gke.io/weighted-load-balancing: "pods-per-node"
cloud.google.com/load-balancer-type: "External"
# Note: GKE uses 'loadBalancerIP' for static IP binding
loadBalancerIP: "<YOUR_STATIC_IP>"
## --- OPTION B: AWS (EKS) ---
# annotations:
# service.beta.kubernetes.io/aws-load-balancer-type: "external"
# service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
# service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
# # Note: AWS uses annotations for Elastic IP binding, NOT 'loadBalancerIP'
# # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: "eipalloc-xxxxxx"
## --- OPTION C: BARE METAL / RKE (MetalLB) ---
# annotations:
# metallb.universe.tf/address-pool: "production-ips"
# metallb.universe.tf/loadBalancerIPs: "<STATIC_IP>"
Deploying the Ingress Controller
Step 1: Verify Cluster Context
Ensure kubectl is connected to the correct Kubernetes cluster:
kubectl config current-context
If the context is not correct, switch to the appropriate one:
kubectl config use-context <YOUR_CONTEXT>
Step 2: Add the Helm Repository
helm repo add traefik https://traefik.github.io/charts
helm repo update
Step 3: Create the Namespace
kubectl create namespace traefik
Step 4: Install Traefik
Run the helm install command, passing your values files:
helm install traefik traefik/traefik \
--namespace traefik \
--version 39.0.0 \
-f traefik-values.yaml \
-f cloud-environment-config.yaml
ℹ️ NOTE: The
-fflag can be specified multiple times. Helm merges the values files in order, with later files taking precedence. This allows the cloud-specific configuration to override the shared core where needed (e.g.,serviceblock).
Step 5: Verify the Deployment
Check that the Traefik pods are running:
CODEkubectl get pods -n traefikVerify the LoadBalancer Service has the expected external IP:
CODEkubectl get svc -n traefik traefikConfirm the
EXTERNAL-IPcolumn matches your reserved static IP.
Updating / Upgrading the Ingress Controller
To upgrade Traefik to a new chart version or apply configuration changes:
Ensure the Helm repository is up to date:
CODEhelm repo updateRun the
helm upgradecommand:CODEhelm upgrade traefik traefik/traefik \ --namespace traefik \ --version 39.0.0 \ -f traefik-values.yaml \ -f cloud-environment-config.yaml
Helm Chart Configuration Options
The following table documents the key configuration values used in the thingsHub Traefik deployment:
Name | Type | Description |
|---|---|---|
High Availability & Resources |
|
|
| integer | Number of Traefik pods. Minimum 2 for HA; recommended |
| boolean | Enables Pod Disruption Budgets for safe node drains. |
| integer | Minimum number of available pods during voluntary disruptions. |
| object | Ensures Traefik pods are scheduled on different nodes using |
| string | Minimum CPU/Memory guaranteed for each Traefik pod. |
| string | Maximum CPU/Memory each Traefik pod can consume. |
Traefik Providers |
|
|
| boolean | Enables the standard Kubernetes Ingress provider. |
| boolean | Allows Ingress resources to route to ExternalName services (e.g., cloud databases). |
| boolean | Returns HTTP 503 when a service has no endpoints instead of failing silently. |
| boolean | Enables Traefik CRDs (IngressRoute, Middleware, etc.). |
| boolean | Allows Traefik to reference Middlewares or TLSOptions defined in other namespaces. |
| boolean | Enables the Kubernetes Gateway API support (Traefik v3). |
Cloud-Specific Networking |
|
|
| string | Service type. Set to |
| string | Must be |
| string | Static IP assignment for the LoadBalancer (used in GKE). |
| object | Cloud provider-specific annotations for the LoadBalancer service. |
Observability & Logging |
|
|
| boolean | Enables Prometheus metrics endpoint. |
| boolean | Adds per-router labels to Prometheus metrics. |
| boolean | Adds per-entrypoint labels to Prometheus metrics. |
| boolean | Creates a Prometheus |
| string | Log level for Traefik (Recommended: |
| boolean | Enables access logging for all incoming requests. |
| string | Format of access logs. Set to |
Global Arguments |
|
|
| list | CLI flags passed to Traefik. |
How to...
... configure for Google Cloud (GKE)
Reserve a Regional Static External IP in your GCP project.
In your
cloud-environment-config.yaml, setservice.loadBalancerIPto the reserved IP.Ensure the following annotations are present:
CODEservice: annotations: cloud.google.com/l4-rbs: "enabled" networking.gke.io/weighted-load-balancing: "pods-per-node" cloud.google.com/load-balancer-type: "External"The
l4-rbsannotation enables modern backend-service based load balancing, which is the recommended mode for GKE.
... configure for AWS (EKS)
Ensure the AWS Load Balancer Controller is installed in the cluster.
Allocate an Elastic IP in AWS.
Use the following annotations:
CODEservice: annotations: service.beta.kubernetes.io/aws-load-balancer-type: "external" service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" # Bind the Elastic IP: # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: "eipalloc-xxxxxx"Note: AWS does not use
loadBalancerIP. Static IP binding is done via theeip-allocationsannotation.
... ensure Source IP preservation
Setting service.spec.externalTrafficPolicy: Local is critical for thingsHub. Without it:
All incoming requests appear to originate from cluster-internal node IPs.
IP-based access controls (e.g.,
system_user_ip_whitelist) will not function correctly.Access logs will not reflect real client IP addresses.
With externalTrafficPolicy: Local, Kubernetes forwards traffic directly to the node running the Traefik pod, preserving the original client IP address.
⚠️ WARNING: This setting requires that each node running a Traefik pod can receive traffic from the load balancer. The Pod Anti-Affinity configuration ensures pods are distributed across nodes, which supports this requirement.
... enable the Traefik Dashboard (Optional)
The Traefik dashboard can be enabled for debugging and observability, but it must be secured.
Generate htpasswd credentials:
CODEhtpasswd -nb admin <your-secure-password>Base64 encode the output:
CODEecho 'admin:<hash>' | base64Create the following Kubernetes manifests and apply them to the
traefiknamespace:BasicAuth Middleware:
CODEapiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: dashboard-auth namespace: traefik spec: basicAuth: secret: dashboard-users-secretSecret for credentials:
CODEapiVersion: v1 kind: Secret metadata: name: dashboard-users-secret namespace: traefik type: Opaque data: users: "<BASE64_ENCODED_HTPASSWD>"Dashboard Ingress:
CODEapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: traefik-dashboard namespace: traefik annotations: traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.middlewares: traefik-dashboard-auth@kubernetescrd traefik.ingress.kubernetes.io/router.tls: "true" spec: ingressClassName: traefik rules: - host: "traefik.<YOUR_DOMAIN>" http: paths: - path: / pathType: Prefix backend: service: name: api@internal port: name: traefik tls: - hosts: - "traefik.<YOUR_DOMAIN>" secretName: traefik-dashboard-tlsApply the manifests:
CODEkubectl apply -f traefik-dashboard-security.yamlVerify the dashboard is accessible at
https://traefik.<YOUR_DOMAIN>.