This is a text-only version of the following page on https://raymii.org: --- Title : Kubernetes (k3s) Ingress for different domains (virtual hosts) Author : Remy van Elst Date : 10-07-2024 20:39 URL : https://raymii.org/s/tutorials/Kubernetes_k3s_Ingress_for_different_domains_like_virtual_hosts.html Format : Markdown/HTML --- Now that I have a [high-available local kubernetes cluster](/s/tutorials/High_Available_k3s_kubernetes_cluster_with_keepalived_galera_and_longhorn.html) it's time to learn not just managing the cluster but actually deploying some services on there. Most examples online use a `NodePort` or a `LoadBalancer` to expose a service on a port, but I want to have domains, like, `grafana.homelab.mydomain.org` instead of `192.0.2.50:3000`. Back in the old days this was called [Virtual Host](https://web.archive.org/web/20240515131604/https://httpd.apache.org/docs/2.4/vhosts/), using 1 IP for multiple domains. My k3s cluster uses `traefik` for its incoming traffic and by defining an `Ingress` we can route a domain to a service (like a `ClusterIP`). This page will show you how.

Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:

I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!

Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.

You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!

Here's a screenshot of `echoapp` running on a resolvable actual domain: ![echoapp](/s/inc/img/echoapp-5.png) The version of [Kubernetes/k3s](https://docs.k3s.io/release-notes/v1.29.X) I use for this article is `v1.29.6+k3s1`. [Ingress] (https://web.archive.org/web/20240613111032/https://kubernetes.io/docs/concepts/services-networking/ingress/) is already being replaced by the [Gateway API] (https://web.archive.org/web/20240605141115/https://kubernetes.io/docs/concepts/services-networking/gateway/) and if using `traefik`, which `k3s` does by default, you have more flexibility with an [IngressRoute] (https://web.archive.org/web/20240508224917/https://doc.traefik.io/traefik/providers/kubernetes-crd/). But, as far as I can tell, `Gateway API` is not really stable yet and for simplicity's sake I'm using `Ingress` instead of `IngressRoute`. If I later want to swap out `traefik` for `nginx` my other stuff should just keep working. I assume you have `k3s` up and running and have `kubectl` configured on your local admin workstation. If not, consult my [previous high available k3s article](/s/tutorials/High_Available_k3s_kubernetes_cluster_with_keepalived_galera_and_longhorn.html) for more info on my specific setup. ### DNS Configuration For this setup to work you must create DNS records pointing to [the high available IP](/s/tutorials/High_Available_k3s_kubernetes_cluster_with_keepalived_galera_and_longhorn.html) of your Kubernetes cluster. I created one regular A record and a wildcard: dig +short k3s.homelab.mydomain.org Output: 192.0.2.50 Same for `*.k3s.homelab.mydomain.org`. Setup differs per domain provider or if you have your own DNS servers so I'm not showing that here. You could, for local purposes, also put the domain name in your local `/etc/hosts` file (and on your k3s nodes as well). ### Deployment For the example I'm using a very simple application, the [echoserver from Marchandise Rudy](https://github.com/Ealenn/Echo-Server). Do note that this app can [read arbitrary files and expose them] (https://github.com/Ealenn/Echo-Server?tab=readme-ov-file#filefolder-explorer), so don't run this somewhere that has sensitive data. Appending the `/?echo_file=/` URL parameter allows you to view any file the app has access to: ![echoapp etc passwd](/s/inc/img/echoapp-1.png) The domain name I'm using is `echo.homelab.mydomain.org`. Create a folder for the yaml files: mkdir echoapp cd echoapp Create a namespace to keep things tidy: kubectl create ns echoapp Create the deployment file: vim echoapp-deployment.yaml Contents: apiVersion: apps/v1 kind: Deployment metadata: name: echo-deployment labels: app: echo spec: replicas: 3 selector: matchLabels: app: echo template: metadata: labels: app: echo spec: containers: - name: echo image: ealen/echo-server:latest ports: - containerPort: 80 livenessProbe: httpGet: path: "/?echo_code=200" port: 80 readinessProbe: httpGet: path: "/?echo_code=200" port: 80 --- apiVersion: v1 kind: Service metadata: name: echo-service spec: ports: - port: 80 selector: app: echo Apply the file: kubectl -n echoapp apply -f echoapp-deployment.yaml This is a fairly standard deployment file with a `Deployment` and a `Service`. I've included a `livenessProbe` and a `readynessProbe` for fun, but in this case those don't offer much of value. In Kubernetes, liveness and readiness probes are used to check the health of your containers. - Liveness Probe: Kubernetes uses liveness probes to know when to restart a container. For instance, if your application had a deadlock and is no longer able to handle requests, restarting the container can make the application more available despite the bug. - Readiness Probe: Kubernetes uses readiness probes to decide when the container is available for accepting traffic. The readiness probe is used to control which pods are used as the backends for services. When a pod is not ready, it is removed from service load balancers. Test the deployment by creating either a `NodePort` or a `LoadBalancer`: kubectl expose service echo-service --type=NodePort --port 9090 --target-port=80 --name=echo-service-np --namespace echoapp or: kubectl expose service echo-service --type=LoadBalancer --port=9191 --target-port=80 --name=echo-service-ext --namespace echoapp Get the newly created port/loadbalancer: kubectl -n echoapp get services Output: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE echo-service ClusterIP 10.43.188.135 80/TCP 9m5s echo-service-ext LoadBalancer 10.43.93.211 192.0.2.61,192.0.2.62,192.0.2.63 9191:30704/TCP 29s echo-service-np NodePort 10.43.10.130 9090:30564/TCP 77s Access that `ip:port` combo in your browser and you should see the app working: ![loadbalancer](/s/inc/img/echoapp-2.png) ### Ingress To make this deployment available via a hostname and not an `ip:port` combo you must create an [Ingress] (https://web.archive.org/web/20240613111032/https://kubernetes.io/docs/concepts/services-networking/ingress/) resource. An `Ingress` needs `apiVersion`, `kind`, `metadata` and `spec` fields. The name of an `Ingress` object must be a valid DNS (sub)domain name. Create the file containing your `Ingress` yaml: vim echoapp-ingress.yaml Contents: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: echo-ingress spec: rules: - host: echo.k3s.homelab.mydomain.org http: paths: - pathType: Prefix path: "/" backend: service: name: echo-service port: number: 80 The file contents are fairly simple and speak for themselves, the most important parts are: - `host: echo.k3s.homelab.mydomain.org` - the DNS domain you want the service to be available on. - `backend.service.name` - must match the `Service` resource - `backend.service.port` - must match the `Service` port Apply the file: kubectl -n echoapp apply -f echoapp-ingress.yaml After a few second you should be able to see your `Ingress`: kubectl -n echoapp get ingress Output: NAME CLASS HOSTS ADDRESS PORTS AGE echo-ingress echo.k3s.homelab.mydomain.org 192.0.2.60,192.0.2.61,192.0.2.62,192.0.2.63 80, 443 2d23h Try to access the domain name in your web-browser, you should see the page right away. ### Traefik 503 instead of 404 on if the targeted Service has no endpoints available. One odd thing I noticed when experimenting with `Ingress` is if your configuration is wrong or you try to access a `Service` which has a failed `Deployment`, you'll get an HTTP 404 error. I'd expect a 503, since there is no server available, not a Not Found error. When there are no `Pods` running with the default config: ![404 error](/s/inc/img/echoapp-3.png) With the "fixed" config: ![503 error](/s/inc/img/echoapp-4.png) To [fix this](https://web.archive.org/web/20240710180745/https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowemptyservices), in the specific `k3s` server setup I use, you must create the following file **on each k3s server node**: vim /var/lib/rancher/k3s/server/manifests/traefik-config.yaml Add the following: apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- dashboard: enabled: true domain: "traefik.k3s.homelab.mydomain.org" providers: kubernetesIngress: allowEmptyServices: true This edits the default `traefik` helm chart used by `k3s` and after `systemctl restart k3s`, you should now get a `503 Service Unavailable` error instead of a `404 Not Found` error when a deployment failed or no pods are running. The fact that you have to edit this file on all `k3s` server nodes is a bummer, but it's fixable and that's nice. --- License: All the text on this website is free as in freedom unless stated otherwise. This means you can use it in any way you want, you can copy it, change it the way you like and republish it, as long as you release the (modified) content under the same license to give others the same freedoms you've got and place my name and a link to this site with the article as source. This site uses Google Analytics for statistics and Google Adwords for advertisements. You are tracked and Google knows everything about you. Use an adblocker like ublock-origin if you don't want it. All the code on this website is licensed under the GNU GPL v3 license unless already licensed under a license which does not allows this form of licensing or if another license is stated on that page / in that software: This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Just to be clear, the information on this website is for meant for educational purposes and you use it at your own risk. I do not take responsibility if you screw something up. Use common sense, do not 'rm -rf /' as root for example. If you have any questions then do not hesitate to contact me. See https://raymii.org/s/static/About.html for details.