# Adding Traefik to Kubernetes As you configure, build, and scale your applications and services in any form of distributed system, you need to ensure that you balance demand on those services across resources. With systems that aren't using Kubernetes, this is typically a [load balancer](https://en.wikipedia.org/wiki/Load_balancing_(computing)) that acts as an entry point for each public service, and distributes requests to resources that can handle them. Instead, Kubernetes uses "[Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)" to achieve the same task. But instead of creating a load balancer for every public service, Ingress is one entry point to all the public services in a Kubernetes cluster. To use an Ingress, you need an Ingress controller, and this is where Traefik comes in. In this blog post, I create a simple application that consists of two services: - [The Containous whoami Docker image](https://github.com/containous/whoami) which is a Go webserver that prints OS information and HTTP requests. - A blog for the whoami service built on [the Ghost blogging platform](https://hub.docker.com/_/ghost). I use [minikube](https://minikube.sigs.k8s.io/docs/) for my cluster, so it's easier to follow along and recreate steps. ## Create the Services The Services for this post are simple ones to focus on the Traefik implementation. First, create a folder to hold the Kubernetes objects called _k8s_. ### whoami Service Create a new file inside the _k8s_ folder called _whoami.yml_, and add the following `Deployment` and `Service` declarations: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: whoami-deployment spec: replicas: 2 selector: matchLabels: app: whoami template: metadata: labels: app: whoami spec: containers: - name: whoami-container image: containous/whoami imagePullPolicy: Always ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: whoami spec: ports: - name: http targetPort: 80 port: 80 selector: app: whoami ``` ### Blog Service For the Ghost blog, create another file called _ghost.yml_ and add the following `Deployment`, `Service`, and `PersistentVolumeClaim`. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: blog labels: app: blog spec: replicas: 1 selector: matchLabels: app: blog template: metadata: labels: app: blog spec: containers: - name: blog image: ghost:3.13.1-alpine imagePullPolicy: Always ports: - containerPort: 2368 env: - name: url value: http://blog.minikube volumeMounts: - mountPath: /var/lib/ghost/content name: content volumes: - name: content persistentVolumeClaim: claimName: blog-content --- apiVersion: v1 kind: Service metadata: name: blog spec: selector: app: blog ports: - name: http port: 80 targetPort: 2368 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: blog-content spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi ``` There's nothing that different in this `Service` from the whoami `Service`, except adding a `PersistentVolumeClaim` that Ghost needs to store content data. ## Add Traefik to the Cluster ### Giving Traefik Permission Traefik uses specific Kubernetes API paths to manage and access the resources it needs. These resources include Services, Endpoints, and Ingresses, and you need to configure [Role Based Access Control (RBAC)](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to allow access to them. Create a new YAML file called _traefik.yml_ and add the following `ClusterRole` object: ```yaml 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 ``` For added security, add a `ClusterRoleBinding` object that restricts the above permissions to only the namespaces (a virtual cluster running on the same physical cluster) that Traefik watches. Add the YAML below to the same _traefik.yml_ file below the current contents: ```yaml --- 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: kube-system ``` ### Deploy Traefik to Cluster You can deploy Traefik to a cluster with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/), depending on your requirements for controlling scaling. I use a Deployment, and you can [find out how to use a DaemonSet in the Traefik documentation](https://docs.traefik.io/v1.7/user-guide/kubernetes/#deploy-traefik-using-a-deployment-or-daemonset). To create the Deployment, you need three different Kubernetes objects, a `ServiceAccount` that defines an account for a process, a `Deployment`, and a `Service`. Add the following to _traefik.yml_ beneath the current contents: ```yaml --- apiVersion: v1 kind: ServiceAccount metadata: name: traefik-ingress-controller namespace: kube-system ``` The important key in this configuration is defining the `namespace` the account has access to. Below, add the following: ```yaml --- kind: Deployment apiVersion: apps/v1 metadata: name: traefik-ingress-controller namespace: kube-system labels: k8s-app: traefik-ingress-lb spec: replicas: 1 selector: matchLabels: k8s-app: traefik-ingress-lb template: metadata: labels: k8s-app: traefik-ingress-lb name: traefik-ingress-lb spec: serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 containers: - image: traefik:v1.7 name: traefik-ingress-lb ports: - name: http containerPort: 80 - name: admin containerPort: 8080 args: - --api - --kubernetes - --logLevel=INFO ``` Crucially here, set the `serviceAccountName` to match the `ServiceAccount` defined above, and define the ports that Traefik uses for its Web UI and to access services exposed via Ingress. Next, add the `Service` for the Ingress itself. It defines and routes both ports: ```yaml --- kind: Service apiVersion: v1 metadata: name: traefik-ingress-service namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb ports: - protocol: TCP port: 80 name: web - protocol: TCP port: 8080 name: admin type: NodePort ``` Add the Traefik Web UI as a separate `Service`, which routes the port it uses back to the Ingress `Service`, as the Ingress handles all incoming requests: ```yaml --- apiVersion: v1 kind: Service metadata: name: traefik-web-ui namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb ports: - name: web port: 80 targetPort: 8080 ``` ### Add Traefik as Ingress With Traefik defined, next, add it to the cluster as an Ingress. Create a new YAML file called _ingress.yml_ and add the following two `Ingress` definitions: ```yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: traefik-web-ui namespace: kube-system spec: rules: - host: traefik-ui.minikube http: paths: - path: / backend: serviceName: traefik-web-ui servicePort: web --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: app-ingress spec: rules: - host: blog.minikube http: paths: - path: / backend: serviceName: blog servicePort: http - host: whoami.minikube http: paths: - path: / backend: serviceName: whoami servicePort: http ``` The `traefik-web-ui` Ingress routes requests to one sub-domain to the Web UI service using the `serviceName` for that `Service` and the matching `servicePort`. As this Ingress routes traffic to the `kube-system` Namespace, you could use further security measures to ensure that only specific traffic sources can access it. The `app-ingress` Ingress routes all other requests, matching a sub-domain to the appropriate service and port. This is an example of [name-based routing](https://docs.traefik.io/v1.7/user-guide/kubernetes/#name-based-routing), you can also configure Traefik to match paths of the same domain, [read the documentation for more details](https://docs.traefik.io/v1.7/user-guide/kubernetes/#path-based-routing). ## Deploying the Cluster With all the configuration files in place, start Minikube, and apply the directory of files with [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/): ```shell kubectl apply -f k8s ``` As the Ingress uses three subdomains for routing, you need to add these to your _/etc/hosts_ file (Linux and macOS) or _c:\windows\system32\drivers\etc\hosts_ file (Windows), matching the domains to the IP address of your Minikube instance. You can find the IP address by running `minikube ip`. For example: ```text 192.168.64.39 traefik-ui.minikube 192.168.64.39 blog.minikube 192.168.64.39 whoami.minikube ``` As we used a Deployment to start Traefik, you need to know the NodePort for the `traefik-ingress-service`. You can find this by running `minikube service list` and noting the port value after the URL in the `URL` column of the Service. For example with a port of `30428` use _http://traefik-ui.minikube:30428_ to access the Web UI, _http://blog.minikube:30428_ to access the blog and _http://whoami.minikube:30428_ to access the whoami Service. {IMGS} ## Next Steps In this article, I covered adding Traefik as an Ingress to a Kubernetes cluster, and some of the configuration options available to suit your needs. For more details on Kubernetes configuration, [read this guide in our documentation](https://docs.traefik.io/v1.7/user-guide/kubernetes) and [the configuration section](https://docs.traefik.io/v1.7/configuration/commons/) for more general options.