Overview

In the previous post, we installed the Longhorn distributed storage system to build a persistent storage environment where data is retained even when pods restart or move to different nodes. This post covers installing the Traefik ingress controller on a homelab Kubernetes cluster and configuring secure access to management interfaces from the internal network.

Traefik Logo

Choosing an Ingress Controller

There are several methods for exposing Kubernetes services externally in a homelab environment:

  1. NodePort: A method for accessing services through specific ports (30000-32767 range) on each node. While simple to configure, it requires remembering port numbers and cannot use standard HTTP/HTTPS ports.

  2. LoadBalancer: A method that uses load balancer implementations like MetalLB to assign dedicated IPs to each service. While standard ports can be used, requiring a separate IP for each service can be inefficient in homelab environments with limited IP resources.

  3. Ingress: A method that defines rules for routing HTTP/HTTPS traffic to services. It provides various features such as URL path and hostname-based routing, SSL/TLS termination, and authentication, allowing multiple services to be exposed through a single IP.

Using an ingress controller allows routing multiple services based on hostnames or paths with just a single IP address and standard ports (80, 443), making it the most suitable method for homelab environments.

Why Traefik Was Chosen

Initially, the Nginx Ingress Controller, the most widely used in the Kubernetes ecosystem, was installed. However, it required separately installing cert-manager for automatic Let’s Encrypt certificate issuance and configuring a ClusterIssuer. Several configuration errors were experienced, particularly with custom headers and middleware configuration.

What is Traefik?

Traefik is a cloud-native reverse proxy and load balancer that Containous (now Traefik Labs) began developing in 2015. It is optimized for microservices environments and Kubernetes, with built-in support for dynamic configuration changes and Let’s Encrypt integration, making it widely used in container orchestration environments.

Eventually, a more integrated solution was sought. Traefik was chosen because it provided all necessary features in a single package, with the following advantages:

  • Configuration Simplicity: The Let’s Encrypt ACME protocol is built-in by default, enabling automatic certificate issuance and renewal without a separate cert-manager.
  • Dashboard Functionality: A built-in web dashboard allows visual monitoring of current routing status, service status, and middleware configuration.
  • Helm Chart Support: An official Helm chart is provided, facilitating declarative deployment in a GitOps manner.
  • CRD Support: CRDs (Custom Resource Definitions) like IngressRoute and Middleware enable finer-grained routing rules and traffic control than standard Ingress.
  • Middleware Capabilities: Various middleware can be declaratively configured for request/response transformation, Basic/Digest authentication, retry, rate limiting, and more.

Separating Internal and External Services

Security is a critical consideration in homelab environments. If cluster management interfaces like ArgoCD, Longhorn dashboard, and Traefik dashboard are exposed to the external internet, they become directly exposed to security threats. To prevent this, a strategy of separating internal management services and external public services with different IP addresses is used.

Network Separation

  1. Internal Load Balancer (192.168.0.200): Exposes only management interfaces like ArgoCD, Longhorn, and Traefik dashboard. Accessible only from within the home network and excluded from router port forwarding targets.

  2. External Load Balancer (192.168.0.201): Exposes only public services like blogs and personal projects. Configured with router port forwarding to be accessible from the external internet.

This design implements separation at the service level. Even if a management interface’s IngressRoute is accidentally connected to an external entrypoint, the IP itself is not routed externally, preventing security incidents. Note that this setup represents service-level separation rather than complete network isolation.

Preparing for Traefik Installation

1. Configuring MetalLB IP Address Pools

Before deploying Traefik, IP address pools must first be configured in MetalLB for internal/external service separation. The following configuration files are created in the GitHub repository.

apps/traefik/templates/ipaddresspool.yaml file:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
    name: traefik-ip-pool
    namespace: metallb-system
spec:
    addresses:
        - 192.168.0.200-192.168.0.201

This manifest defines an IP address pool that MetalLB can allocate to LoadBalancer-type services, including two IPs for internal (192.168.0.200) and external (192.168.0.201) use. These IP addresses should be excluded from the home network’s DHCP server allocation range to prevent IP conflicts.

apps/traefik/templates/l2advertisement.yaml file:

apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
    name: traefik-l2-advertisement
    namespace: metallb-system
spec:
    ipAddressPools:
        - traefik-ip-pool

This manifest defines MetalLB’s Layer 2 mode advertisement configuration, allowing IPs from the defined IP address pool to be advertised on the network via the ARP protocol so traffic can be routed to the correct nodes.

2. Helm Chart Configuration

apps/traefik/Chart.yaml file:

apiVersion: v2
name: traefik
description: Traefik Ingress Controller for Kubernetes
type: application
version: 1.0.0
appVersion: "v3.2.2"
dependencies:
    - name: traefik
      version: "33.2.1"
      repository: "https://traefik.github.io/charts"

This file defines fetching and installing the v33.2.1 chart from the official Traefik Helm chart repository.

The apps/traefik/values.yaml file contains detailed Traefik settings. The key configurations are examined below.

Internal/External Entrypoint Configuration

ports:
    web:
        port: 8000
        expose:
            default: true
            internal: false
        exposedPort: 80
        protocol: TCP
    websecure:
        port: 8443
        expose:
            default: true
            internal: false
        exposedPort: 443
        protocol: TCP
        tls:
            enabled: true
            certResolver: "letsencrypt"
    intweb:
        port: 8001
        expose:
            default: false
            internal: true
        exposedPort: 80
        protocol: TCP
    intwebsec:
        port: 8444
        expose:
            default: false
            internal: true
        exposedPort: 443
        protocol: TCP
        tls:
            enabled: true
            certResolver: "letsencrypt"

Here, web and websecure are external entrypoints exposed through the external load balancer (192.168.0.201), while intweb and intwebsec are internal entrypoints accessible only through the internal load balancer (192.168.0.200). Each entrypoint uses different ports internally but is exposed externally on standard HTTP (80) and HTTPS (443) ports.

Let’s Encrypt Configuration

certificatesResolvers:
    letsencrypt:
        acme:
            email: [email protected]
            httpChallenge:
                entryPoint: web
            storage: /data/acme.json

This configuration sets up automatic SSL/TLS certificate issuance and renewal using the Let’s Encrypt ACME protocol. The HTTP-01 challenge method uses HTTP requests entering through the web entrypoint to prove control over the domain. Certificate issuance will work properly after external access configuration is completed, which will be covered in the next post.

Internal/External Service Separation

service:
    enabled: true
    single: true
    type: LoadBalancer
    annotations:
        metallb.universe.tf/loadBalancerIPs: 192.168.0.201
    additionalServices:
        internal:
            type: LoadBalancer
            annotations:
                metallb.universe.tf/loadBalancerIPs: 192.168.0.200
            labels:
                traefik-service-type: internal

This configuration creates two separate LoadBalancer services:

  1. Default Service (traefik): Assigned IP address 192.168.0.201 to handle traffic for externally accessible public services.
  2. Internal Service (traefik-internal): Assigned IP address 192.168.0.200 to handle traffic for management interfaces accessible only from the internal network.

The metallb.universe.tf/loadBalancerIPs annotation instructs MetalLB to assign specific IP addresses to the respective services.

Persistent Volume Configuration for Certificate Storage

deployment:
    initContainers:
        - name: volume-permissions
          image: busybox:1.36
          command:
              [
                  "sh",
                  "-c",
                  "touch /data/acme.json; chmod -v 600 /data/acme.json; adduser -S 65532 65532; chown -R 65532:65532 /data/acme.json",
              ]
          volumeMounts:
              - name: data
                mountPath: /data

persistence:
    enabled: true
    accessMode: ReadWriteOnce
    size: 128Mi
    storageClass: longhorn

podSecurityContext:
    fsGroup: 65532
    fsGroupChangePolicy: "OnRootMismatch"
    runAsGroup: 65532
    runAsNonRoot: true
    runAsUser: 65532

This configuration sets up a persistent volume for storing Let’s Encrypt certificates using the Longhorn storage class. The init container sets appropriate permissions (600) and ownership on the ACME certificate file, allowing Traefik to securely store and manage certificates. Certificates are retained even when pods restart or move to different nodes.

3. Deploying with GitOps

Committing and pushing the configuration files to the Git repository triggers ArgoCD to automatically detect changes and deploy to the cluster:

git add apps/traefik
git commit -m "Add Traefik ingress controller with internal/external separation"
git push origin main

Verify that deployment is complete:

kubectl get pods -n traefik

The Traefik pod should appear in Running status:

NAME                       READY   STATUS    RESTARTS   AGE
traefik-5d7b9b4f6c-xtz89   1/1     Running   0          5m

Verify that two LoadBalancer services have been created:

kubectl get svc -n traefik
NAME               TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
traefik            LoadBalancer   10.43.xxx.xxx   192.168.0.201   80:xxxxx/TCP,443:xxxxx/TCP   5m
traefik-internal   LoadBalancer   10.43.xxx.xxx   192.168.0.200   80:xxxxx/TCP,443:xxxxx/TCP   5m

Configuring Internal Service Access

Now that Traefik is installed, IngressRoutes are configured to access internal management interfaces like ArgoCD, Longhorn, and Traefik dashboard. These routes use internal entrypoints (intweb, intwebsec) to be accessible only from the internal network.

1. Configuring Internal Service Routing

apps/argocd/templates/ingressroute.yaml file for ArgoCD access:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
    name: argocd-server-internal
    namespace: argocd
spec:
    entryPoints:
        - intweb
        - intwebsec
    routes:
        - match: Host(`argocd.injunweb.com`)
          kind: Rule
          services:
              - name: argocd-server
                port: 80

This manifest routes requests for the argocd.injunweb.com host to port 80 of the ArgoCD server service through internal entrypoints.

apps/longhorn-system/templates/ingressroute.yaml file for Longhorn UI:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
    name: longhorn-frontend-internal
    namespace: longhorn-system
spec:
    entryPoints:
        - intweb
        - intwebsec
    routes:
        - match: Host(`longhorn.injunweb.com`)
          kind: Rule
          services:
              - name: longhorn-frontend
                port: 80

This manifest routes requests for the longhorn.injunweb.com host to the Longhorn frontend service through internal entrypoints.

The Traefik dashboard was configured directly in the Helm chart’s values.yaml:

ingressRoute:
    dashboard:
        enabled: true
        matchRule: Host(`traefik.injunweb.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
        services:
            - name: api@internal
              kind: TraefikService
        entryPoints: ["intweb", "intwebsec"]

This configuration enables access to Traefik’s internal API service (api@internal) through the traefik.injunweb.com/dashboard path to use the dashboard.

Add the created manifest files to the Git repository:

git add apps/argocd/templates/ingressroute.yaml
git add apps/longhorn-system/templates/ingressroute.yaml
git commit -m "Add internal IngressRoutes for admin interfaces"
git push origin main

2. Local Hosts File Configuration

Modify the local computer’s hosts file to access internal services by domain name.

Linux/macOS:

sudo vim /etc/hosts

Windows (administrator privileges required):

C:\Windows\System32\drivers\etc\hosts

Add the following line to the hosts file:

192.168.0.200 traefik.injunweb.com argocd.injunweb.com longhorn.injunweb.com

This configuration resolves the domain names to the internal load balancer IP (192.168.0.200), allowing direct access to internal services without going through a DNS server.

Testing Access

With all configurations complete, test whether each service is accessible from the internal network.

Access the following URLs in a web browser and verify that each service displays properly:

  • http://traefik.injunweb.com/dashboard/ - Traefik dashboard
  • http://argocd.injunweb.com - ArgoCD UI
  • http://longhorn.injunweb.com - Longhorn UI

If all services are properly accessible, the internal service configuration is complete. In the current state, these services are connected only to the internal IP (192.168.0.200), so they are not accessible from the external internet.

Conclusion

This post covered installing the Traefik ingress controller on a homelab Kubernetes cluster and configuring secure access to management interfaces by separating internal and external services. By utilizing MetalLB’s IP address pools to separate internal and external load balancers, exposure of management interfaces to the outside can be prevented.

The next post covers configuring DDNS and port forwarding to make homelab services accessible from the external internet using the external load balancer.

Next Post: Mini PC Kubernetes #5: External Access