Tutorial : Traefik 2.0 - Baremetal - RancherOS - Hetzner - OVH

Hello,

Last time i’ve check, Traefik wasn’t working out of the box on the Rancher Products.
This is following my another here about RancherOS/Rancher.

  • Using Helm package didn’t get our Ingress directives as ready on the UI
  • Using the guide on the traefik wasn’t working as it is not up to date of the 2.0 Rancher release.
  • We are going to use the lastest release of Traefik 2.0

So what can we do ? Try, try and try until we figure it out

But first a bet of reminder about what is Traefik:

Traefik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. Traefik integrates with your existing infrastructure components (Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS, …) and configures itself automatically and dynamically. Pointing Traefik at your orchestrator should be the only configuration step you need

Disclaimer

We well not talk about how to set or use Rancher products and associate tools. You need to be aware of how to use Rancher Products and kubectl tool.
This topic could be upgraded at any times regarding feedback so don’t hesitate to comment.

Objectives

Running Rancher on top of a single RancherOS server i needed to have a simple solution in order to expose my services to the WWW. Traefik have a single feature that i didn’t found on any other (yet) Ingress controller (Ambassador, Istio, Haproxy, External-DNS etc…) which is to be able to use Letsencrypt DNS Challenge with a support of OVH as a provider.

Let’s do it :wink::

Beware that i’ve been stuck to make network communication worked between projects as i’m using Canal as my network provider with “Project Network Isolation” set to Enabled at first. In order to manager/organize your application between several projects like backend/frontend/backoffice etc you will need to set this settings to false and manage Network Policy to restrict your network flows.

  1. Create a dedicated ns ( or not it’s up to you but i recommend it ) without a restricting policy in place as we are going to bind to restricted port ( 80/443/22 )
kubectl create ns traefik
  1. Create the Traefik Custom Ressources Definitions
    This is a new configuration required regarding Traefik 1.7.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

  1. RBAC Settings
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - ingressroutes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - ingressroutetcps
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - traefik.containo.us
    resources:
      - tlsoptions
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: traefik
    
  1. Traefik Configuration file

Traefik 2.0 can use static file & dynamic configuration through labels and annotations. In our case we are going an traefik.yml file.

## Static configuration
entryPoints:
  web:
    address: ":8000"

  web-secure:
    address: ":4443"
  
  ssh:
    address: ":2222"

certificatesResolvers:
  default:
    acme:
      email: YOUREMAIL@acme.com
      storage: /data/acme.json
      dnsChallenge:
        provider: ovh
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

providers:
  kubernetesCRD:
    namespaces:
      - "default"
      - "production"
      - "traefik"
      - "x"
      - "y"
  file:
    directory: /local/traefik-file-provider

api:
  dashboard: true

accessLog: {}

traefik-file-provider.yml

http:
  routers:
    api-dashboard:
      entryPoints:
        - "web-secure"
      rule :  Host(`traefik.example.acme`) && PathPrefix(`/api`) || Host(`traefik.example.acme`) && PathPrefix(`/dashboard`)
      tls:
        certResolver: default
      service: api@internal
      middlewares:
        - auth-dashboard
  middlewares:
    auth-dashboard:
      basicAuth:
        users:
          - "test:$apr1$Jse1O.XT$zJu7lNeaM1zJT8PuZMrvW1" 
          - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
kubectl create configmap traefik-config-yml --from-file=./config-maps/traefik -n traefik
  1. Environment variables

To be able to reach OVH Api you need to have some api crendential in order to the Trafik Controller to reach and interact with your OVH Account.
Have a look on thoses links:

Let’s create the secret file which will have my api keys.

apiVersion: v1
kind: Secret
metadata:
  name: ovh-credentials
type: Opaque
data:
  ovh_endpoint: BASE64
  ovh_application_key: BASE64
  ovh_application_secret: BASE64
  ovh_consumer_key: BASE64

Now that we have our file let’s import it on the Rancher

kubectl create -n traefik -f traefik-ovh-secret.yml
  1. Deployment of Traefik

Check using kubectl get pods -n traefik to see how your’re pods are going on and to check the logs if any goes wrong.

I’m using the same logic as the official Traefik documentation.

apiVersion: v1
kind: Service
metadata:
  name: traefik

spec:
  ports:
    - protocol: TCP
      name: web
      port: 8000
    - protocol: TCP
      name: admin
      port: 8080
    - protocol: TCP
      name: websecure
      port: 4443
    - protocol: TCP
      name: ssh
      port: 2222
  selector:
    app: traefik

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

spec:
  ports:
    - protocol: TCP
      name: web
      port: 80
  selector:
    app: whoami
    
---

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: traefik
  name: traefik-ingress-controller

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          env:
          - name: TZ
            value: 'Europe/Brussels'
          - name: OVH_ENDPOINT
            valueFrom:
              secretKeyRef:
                name: ovh-credentials
                key: ovh_endpoint
          - name: OVH_APPLICATION_KEY
            valueFrom:
              secretKeyRef:
                name: ovh-credentials
                key: ovh_application_key
          - name: OVH_APPLICATION_SECRET
            valueFrom:
              secretKeyRef:
                name: ovh-credentials
                key: ovh_application_secret
          - name: OVH_CONSUMER_KEY
            valueFrom:
              secretKeyRef:
                name: ovh-credentials
                key: ovh_consumer_key
          image: traefik:v2.0
          args:
            - --configFile=/local/traefik/traefik.yml
          ports:
            - name: web
              containerPort: 8000
              hostPort: 80
            - name: websecure
              containerPort: 4443
              hostPort: 443
              protocol: TCP
            - name: ssh
              containerPort: 2222
              hostPort: 22
              protocol: TCP
            - name: admin
              containerPort: 8080
          volumeMounts:
          - mountPath: /local/traefik
            name: traefik-config
          - mountPath: /local/traefik-file-provider
            name: traefik-file-provider
          - mountPath: /data
            name: traefik-ssl-storage
      volumes:
      - configMap:
          defaultMode: 256
          name: traefik-config-yml
          optional: false
        name: traefik-config
      - configMap:
          defaultMode: 256
          name: traefik-file-provider
          optional: false
        name: traefik-file-provider
      - hostPath:
          path: /mnt/system/traefik-data
          type: DirectoryOrCreate
        name: traefik-ssl-storage
              

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: whoami
  labels:
    app: whoami

spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: containous/whoami
          ports:
            - name: web
              containerPort: 80

  1. IngressRoute

Since the 2.0 i didn’t find a way throught the documentation of the use of acme certificate generation using classic ingress definition. I’ve been force to move my ingress to an IngressRoute which is not available through the UI of Rancher.

As the documentation say, let’s deploy an IngressRoute for the whoami container.


apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: simpleingressroute
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`whoami.YOURDOMAIN.com`) && PathPrefix(`/notls`)
    kind: Rule
    services:
    - name: whoami
      port: 80

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls
spec:
  entryPoints:
    - web-secure
  routes:
  - match: Host(`whoami.YOURDOMAIN.com`) && PathPrefix(`/tls`)
    kind: Rule
    services:
    - name: whoami
      port: 80
  tls:
    certResolver: default
  1. Way of doing storage related to security

As you saw i’ve been only using “hostPath” instruction for most of my examples.
This could be changing on VolumesClaims to classic volumes which could also use “hostPath”.
Using VolumeClaims will let you use restricted policy inside of your Rancher and let you feel more safe about yours pods

RancherOS SSH Port Settings

Open a console into your rancheros server then use a file like :

rancher:
  ssh:
    port: 2022

Then use ros config merge -i cloud-ssh.yml in order to change the ssh port of your server.

Gitlab Repository

I’ve created an Gitlab repository in order to host my apps yaml files to provide live example.

Bibliography

https://docs.traefik.io/
https://docs.traefik.io/user-guides/crd-acme https://github.com/helm/charts/tree/master/stable/traefik
https://medium.com/nephely/configure-traefik-for-the-dns-01-challenge-with-ovh-as-dns-provider-c737670c0434