Skip to main content
Skip table of contents

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:

  1. Kubernetes Cluster: A running Kubernetes cluster (GKE, EKS, or RKE).

  2. Helm: Helm v3.10+ must be installed. (Install Helm)

  3. kubectl: kubectl must be installed and configured to connect to the target cluster.

  4. 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:

      CODE
      gcloud 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.

CODE
# ==========================================
# 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.

CODE
# ==========================================
# 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:

CODE
kubectl config current-context

If the context is not correct, switch to the appropriate one:

CODE
kubectl config use-context <YOUR_CONTEXT>

Step 2: Add the Helm Repository

CODE
helm repo add traefik https://traefik.github.io/charts
helm repo update

Step 3: Create the Namespace

CODE
kubectl create namespace traefik

Step 4: Install Traefik

Run the helm install command, passing your values files:

CODE
helm install traefik traefik/traefik \
  --namespace traefik \
  --version 39.0.0 \
  -f traefik-values.yaml \
  -f cloud-environment-config.yaml

ℹ️ NOTE: The -f flag 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., service block).

Step 5: Verify the Deployment

  1. Check that the Traefik pods are running:

    CODE
    kubectl get pods -n traefik
  2. Verify the LoadBalancer Service has the expected external IP:

    CODE
    kubectl get svc -n traefik traefik
  3. Confirm the EXTERNAL-IP column matches your reserved static IP.


Updating / Upgrading the Ingress Controller

To upgrade Traefik to a new chart version or apply configuration changes:

  1. Ensure the Helm repository is up to date:

    CODE
    helm repo update
  2. Run the helm upgrade command:

    CODE
    helm 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

 

 

deployment.replicas

integer

Number of Traefik pods. Minimum 2 for HA; recommended 3.

podDisruptionBudget.enabled

boolean

Enables Pod Disruption Budgets for safe node drains.

podDisruptionBudget.minAvailable

integer

Minimum number of available pods during voluntary disruptions.

affinity.podAntiAffinity

object

Ensures Traefik pods are scheduled on different nodes using topologyKey: kubernetes.io/hostname.

resources.requests.cpu / memory

string

Minimum CPU/Memory guaranteed for each Traefik pod.

resources.limits.cpu / memory

string

Maximum CPU/Memory each Traefik pod can consume.

Traefik Providers

 

 

providers.kubernetesIngress.enabled

boolean

Enables the standard Kubernetes Ingress provider.

providers.kubernetesIngress.allowExternalNameServices

boolean

Allows Ingress resources to route to ExternalName services (e.g., cloud databases).

providers.kubernetesIngress.allowEmptyServices

boolean

Returns HTTP 503 when a service has no endpoints instead of failing silently.

providers.kubernetesCRD.enabled

boolean

Enables Traefik CRDs (IngressRoute, Middleware, etc.).

providers.kubernetesCRD.allowCrossNamespace

boolean

Allows Traefik to reference Middlewares or TLSOptions defined in other namespaces.

experimental.kubernetesGateway.enabled

boolean

Enables the Kubernetes Gateway API support (Traefik v3).

Cloud-Specific Networking

 

 

service.type

string

Service type. Set to LoadBalancer to expose Traefik externally.

service.spec.externalTrafficPolicy

string

Must be Local to preserve the source IP address of client requests.

service.loadBalancerIP

string

Static IP assignment for the LoadBalancer (used in GKE).

service.annotations

object

Cloud provider-specific annotations for the LoadBalancer service.

Observability & Logging

 

 

metrics.prometheus.enabled

boolean

Enables Prometheus metrics endpoint.

metrics.prometheus.addRoutersLabels

boolean

Adds per-router labels to Prometheus metrics.

metrics.prometheus.addEntryPointsLabels

boolean

Adds per-entrypoint labels to Prometheus metrics.

metrics.prometheus.serviceMonitor.enabled

boolean

Creates a Prometheus ServiceMonitor resource. Set to true if the Prometheus Operator is present.

logs.general.level

string

Log level for Traefik (Recommended: WARN). Options: DEBUG, INFO, WARN, ERROR.

logs.access.enabled

boolean

Enables access logging for all incoming requests.

logs.access.format

string

Format of access logs. Set to json for structured logging.

Global Arguments

 

 

additionalArguments

list

CLI flags passed to Traefik. --global.checknewversion=false and --global.sendanonymoususage=false disable telemetry.


How to...

... configure for Google Cloud (GKE)

  1. Reserve a Regional Static External IP in your GCP project.

  2. In your cloud-environment-config.yaml, set service.loadBalancerIP to the reserved IP.

  3. Ensure the following annotations are present:

    CODE
    service:
      annotations:
        cloud.google.com/l4-rbs: "enabled"
        networking.gke.io/weighted-load-balancing: "pods-per-node"
        cloud.google.com/load-balancer-type: "External"
  4. The l4-rbs annotation enables modern backend-service based load balancing, which is the recommended mode for GKE.

... configure for AWS (EKS)

  1. Ensure the AWS Load Balancer Controller is installed in the cluster.

  2. Allocate an Elastic IP in AWS.

  3. Use the following annotations:

    CODE
    service:
      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"
  4. Note: AWS does not use loadBalancerIP. Static IP binding is done via the eip-allocations annotation.

... 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.

  1. Generate htpasswd credentials:

    CODE
    htpasswd -nb admin <your-secure-password>
  2. Base64 encode the output:

    CODE
    echo 'admin:<hash>' | base64
  3. Create the following Kubernetes manifests and apply them to the traefik namespace:

    BasicAuth Middleware:

    CODE
    apiVersion: traefik.io/v1alpha1
    kind: Middleware
    metadata:
      name: dashboard-auth
      namespace: traefik
    spec:
      basicAuth:
        secret: dashboard-users-secret

    Secret for credentials:

    CODE
    apiVersion: v1
    kind: Secret
    metadata:
      name: dashboard-users-secret
      namespace: traefik
    type: Opaque
    data:
      users: "<BASE64_ENCODED_HTPASSWD>"

    Dashboard Ingress:

    CODE
    apiVersion: 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-tls
  4. Apply the manifests:

    CODE
    kubectl apply -f traefik-dashboard-security.yaml
  5. Verify the dashboard is accessible at https://traefik.<YOUR_DOMAIN>.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.