Skip to content

Setup Traefik with MetalLB

Requirements

  • Kubernetes
  • MetalLB
  • Traefik
  • Helm

Install Traefik using Helm

bash
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm install traefik traefik/traefik

To install crds go here Copy and execute commands in IngressRoute Definition.

Install metallb

sh
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml

MetallB setup

metallb-pool.yml

yml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: traefik-pool-public
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.201/32 # Exclude this IP from DHCP and NAT it to WAN
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: traefik
  namespace: metallb-system
spec:
  ipAddressPools:
    - traefik-pool-public

Traefik with MetalLB example setup

namespace.yml

yml
apiVersion: v1
kind: Namespace
metadata:
  name: traefik

account.yml

yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-account
  namespace: traefik

role.yml

yml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-role
  namespace: traefik
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - secrets
      - nodes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - discovery.k8s.io
    resources:
      - endpointslices
    verbs:
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.io
    resources:
      - middlewares
      - middlewaretcps
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
      - serverstransports
      - serverstransporttcps
    verbs:
      - get
      - list
      - watch

cluster-role-binding.yml

yml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-role-binding
  namespace: traefik
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-role
subjects:
  - kind: ServiceAccount
    name: traefik-account
    namespace: traefik

traefik-deployment.yml

yml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik-deployment
  labels:
    app: traefik
  namespace: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
      namespace: traefik
    spec:
      serviceAccountName: traefik-account
      containers:
        - name: traefik
          image: traefik:v3.4
          args:
            - --api.insecure
            - --providers.kubernetesingress
          ports:
            - name: web
              containerPort: 80
            - name: dashboard
              containerPort: 8080

traefik-service.yml

yml
apiVersion: v1
kind: Service
metadata:
  name: traefik-web-service
  annotations:
    metallb.io/address-pool: traefik-pool-public

spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      name: web
      port: 80
    - protocol: TCP
      name: admin
      port: 8080
    - protocol: TCP
      name: websecure
      port: 443
  selector:
    app: traefik

Whoami app example

whoami-deployment.yml

yml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoami
  labels:
    app: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami
          ports:
            - name: web
              containerPort: 80

whoami-ingress.yml

yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami-ingress
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: whoami
                port:
                  name: web

whoami-service.yml

yml
apiVersion: v1
kind: Service
metadata:
  name: whoami

spec:
  ports:
    - name: web
      port: 80
      targetPort: web

  selector:
    app: whoami

You can use the dashboard of Traefik to see the status of your services and routes. You need to access it using a port forward to the Traefik pod. Because we don't expose the dashboard to our public metalLB IP.

HTTPS setup

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: default
  name: traefik-ingress-controller
---
kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: default
  name: traefik
  labels:
    app: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      nodeSelector:
        node-type: storage
    spec:
      serviceAccountName: traefik-ingress-controller
      initContainers:
        - name: init-acme
          image: busybox
          command: ["/bin/sh", "-c"]
          args:
            - touch /data/acme.json && chmod 600 /data/acme.json
          volumeMounts:
            - name: acme-storage
              mountPath: /data
      containers:
        - name: traefik
          image: traefik:v3.4
          args:
            - --api.insecure=true
            - --accesslog
            - --entryPoints.web.address=:80
            - --entryPoints.websecure.address=:443
            - --providers.kubernetescrd
            - --certificatesresolvers.myresolver.acme.httpchallenge=true
            - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
            - --certificatesresolvers.myresolver.acme.email=olivierargentieri@gmail.com
            - --certificatesresolvers.myresolver.acme.storage=/data/acme.json
          ports:
            - name: web
              containerPort: 80
            - name: websecure
              containerPort: 4443
            - name: admin
              containerPort: 8080
          volumeMounts:
            - name: acme-storage
              mountPath: /data

      volumes:
        - name: acme-storage
          persistentVolumeClaim:
            claimName: acme-pvc
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: traefik-retain-storage
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: acme-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: traefik-retain-storage
  hostPath:
    path: "/mnt/traefik/acme"
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: node-type
              operator: In
              values:
                - storage
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: acme-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: traefik-retain-storage


crds ingress route tls/notls.
```yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: simpleingressroute
  namespace: default
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`olivierargentieri.ddns.net`) && PathPrefix(`/notls`)
    kind: Rule
    services:
    - name: whoami
      port: 80
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`olivierargentieri.ddns.net`) && PathPrefix(`/tls`)
    kind: Rule
    services:
    - name: whoami
      port: 80
  tls:
    certResolver: myresolver # This is the resolver we defined in the Traefik deployment arguments.

To set up HTTPS, you can use Let's Encrypt with Traefik. You need to add the following arguments to the Traefik deployment:

References

Bonus setup new app

Becareful to use the correct selector and matchLabels in the deployment and service. For securirty reasons, trafik can't redirect connections to other namespaces.

yml
apiVersion: v1
kind: Secret
metadata:
  name: ghcr-secret
data:
  .dockerconfigjson: <base64_hash> # see https://docs.olivierargentieri.fr/it/kubernetes/githubcredential.html#create-a-github-token
type: kubernetes.io/dockerconfigjson
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-iptv
  labels:
    app: iptv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: iptv
  template:
    metadata:
      labels:
        app: iptv
    spec:
      containers:
        - name: app-iptv
          image: ghcr.io/olivierargentieri/agiptv:latest
          ports:
            - name: web
              containerPort: 80
              protocol: TCP
          resources:
            limits:
              cpu: 200m
              memory: 256Mi
            requests:
              cpu: 100m
              memory: 128Mi
          imagePullPolicy: Always
      restartPolicy: Always
      imagePullSecrets:
        - name: ghcr-secret
---
apiVersion: v1
kind: Service
metadata:
  name: svc-iptv
spec:
  ports:
    - name: web
      port: 80
      targetPort: web
  selector:
    app: iptv
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-iptv
spec:
  entryPoints:
    - websecure # this is important to use the correct entry point for HTTPS.
  routes:
    - match: Host(`olivierargentieri.ddns.net`) && PathPrefix(`/iptv`)
      kind: Rule
      services:
        - name: svc-iptv
          port: 80
  tls:
    certResolver: myresolver