Single-node Kubernetes with ingresses ☸️
For a friend, I did a Telegram bot and containerized the app with Docker. In reality, that was the beginning of a long struggle to keep only Docker and configure a reverse proxy, because I’m really bad at that.
Instead, I installed k3s, which allowed me to have single-node Kubernetes, meaning all running on a single VM. Kubernetes makes it possible to orchestrate containers, thus solving many of my issues.
"k3s" banner generated by Flux with Perplexity
How to do it
This tutorial is nothing amazing, but I made it because k3s doesn’t come with all the features I needed. Here’s how to proceed.
- Have a Linux VM. At least 2 CPUs and 2 GB of RAM are sufficient
- Make sure ports 80 and 443 are open inbound (NSG, firewall, iptables, ufw, depending on what you have)
- Install Docker (unless containerD is enough for you)
- Install k3s:
curl -sfL https://get.k3s.io | sh -
- Enable k3s on next reboot with
sudo systemctl enable k3s
- Add the line
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
to your rc file (.bashrc, .zshrc, etc.)- Note: if you encounter problems, you can relax the permissions of the k3s file with
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
(not recommended)
- Note: if you encounter problems, you can relax the permissions of the k3s file with
- Install ingress-nginx for your Ingress resources (if using a cloud provider) with
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml
- Install cert-manager to enable HTTPS later with
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml
- Install MetalLB to create LoadBalancers/Ingresses with
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
.yaml
files below, you can apply them with the command:kubectl apply -f myfile.yaml
Configuring MetalLB
MetalLB needs to be able to create LoadBalancers using a pool of available IPs. To determine which range to use:
- Run
hostname -I
. - Pick a range of addresses that doesn’t overlap with the listed IPs.
Create and apply the file metallb.yaml
:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 10.0.0.240-10.0.0.250 # adjust according to your network
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advert
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
Configuring Let’s Encrypt
Let’s Encrypt must confirm that you own your domain names. Create a file clusterissuer-letsencrypt.yaml
:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: timothe@chauvet.cloud # replace this
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: traefik # replace this if you want nginx
If you need a certificate, you can create one with a file like certificate-yourls.yaml
:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: yourls-tls
namespace: default
spec:
secretName: yourls-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- tmth.fr
Hosting My Apps
My Telegram Bot
For my Telegram bot, it was quite simple because it doesn’t need to receive HTTP requests. A simple Deployment was enough:
apiVersion: apps/v1
kind: Deployment
metadata:
name: telebot
spec:
replicas: 1
selector:
matchLabels:
app: telebot
template:
metadata:
labels:
app: telebot
spec:
restartPolicy: Always
containers:
- name: telebot
image: ghcr.io/timothechauvet/telebot:latest
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: telebot-secrets
key: TELEGRAM_BOT_TOKEN
imagePullSecrets:
- name: ghcr-secret
An HTTP App
I wanted an URL shortener. You may have already seen it by clicking on tmth.fr/k3s to come here! I used Yourls for that. It requires a MySQL database. That was the trickiest part to set up.
For Yourls, I needed:
- A ConfigMap with
config.php
- A Certificate for my domain tmth.fr
- A Traefik Ingress (I prefer that because Nginx is for smart people 😭)
- The Service to map to the Ingress
- A PersistentVolume and its claim for the MySQL database
- The Deployment, of course.
I think the most important one is the Ingress which creates rules for when a request to tmth.fr is made
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: yourls-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
traefik.ingress.kubernetes.io/router.entrypoints: "web,websecure"
spec:
ingressClassName: traefik
tls:
- hosts:
- tmth.fr
secretName: yourls-tls
rules:
- host: tmth.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: yourls
port:
number: 80
I’m obviously not going to publish ALL my configurations here.
Alas!!
I published my configurations, that you can find on my GitHub. Feel free to use them as examples!
If you have any questions or suggestions, don’t hesitate to contact me by email, on LinkedIn, or by sending an issue on GitHub.