Hello World in K3S (slightly updated)
So messing with k3s on one of my home linux boxes and googled for a hello-world type of example just to make sure it was running (I’d just uninstalled and re-installed everything). I was somewhat surprised to see the top result was Jeff Geerling’s post about it. I’m a big fan of Jeff’s as I’ve been watching his YouTube videos for quite some time. Anyway, after looking over his post, I decided to update his scripts slightly with a minor fix, some references to namespacing and also deleting it, just for completeness sake.
Initial Setup and Overview
We’re assuming you already have k3s (or something k8s flavor installed and configured enough that you can do kubectl
commands).
What are we creating? In short – we’re deploying nginx pods (3 replicas) and handling incoming traffic on port 80 (non-TLS) to route to those pods. To try to use some official kubernetes terms. We’ll be defining:
- service (called
hello-world
) - a deployment (called
hello-world-nginx
) ➡️ - a
hello-world
ReplicaSet which is defined as just the nginx container. We’re only exposing port 80 in the container. - a volume mount where the
hello-world-volume
is mounted at/usr/share/nginx/html
(which is the default path where nginx looks for static content to serve). The volume defined using a configMap which is also being calledhello-world
.
note
We’ll just use the crap out of the name, hello-world
, because naming things is hard™️. So almost everything is named hello-world
. It might have been interesting to name things based on the type of resource they were, e.g. hello-world-replicaset
or hello-world-volume
but since that’s maybe not so typical, just kept it mostly as Jeff had it which tends to line up with other examples in the documentation as well. But it’s good to keep in mind the different things we’re defining here.
recommendation
I recommend putting the files you’re going to create in their own directory for your own sanity.
The Namespace
So the first thing I would tend to do is to create namespace for this service and deployment to run in so that’s not in just the default or current namespace.
kubectl create namespace hello-world
Once again, we’re abusing the hello-world
name and also using that as our namespace to run in.
At this point, you could then also set that to be the default namespace in your current context, but I’m skipping that, and just specifying the --namespace
on each of these commands. If you want to just use the default
namespace, you can leave off the --namespace
part.
The Content to Serve / ConfigMap
This is for the volume mapping / mount, etc. It also happens to be our actual content.
Save this file as index.html
<html>
<head>
<title>Hello World!</title>
</head>
<body>Hello World!</body>
</html>
Then to create a ConfigMap based off that file:
kubectl create configmap hello-world --from-file index.html --namespace=hello-world
Service / deployment
Create a file hello-world.yml
:
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-world
annotations:
spec.ingressClassName: "traefik"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-world
spec:
ports:
- port: 80
protocol: TCP
selector:
app: hello-world
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-nginx
spec:
selector:
matchLabels:
app: hello-world
replicas: 3
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: hello-world-volume
mountPath: /usr/share/nginx/html
volumes:
- name: hello-world-volume
configMap:
name: hello-world
Note, my fix here from Jeff’s version is line 7:
spec.ingressClassName: "traefik"
To now deploy this:
kubectl apply -f hello-world.yml --namespace=hello-world
Once that executes correctly, from any machine on the network that can reach your server running k3s, you should be able to hit port 80 and get the hello world web page above:
curl yourserver.local:80
Or, use a web browser.
Looking At Resources
If you want to see all these resources that have been created, we can use kubectl
to examine what we have running.
At the pod, level, you should see something similar to this:
$ kubectl get pods --namespace=hello-world
NAME READY STATUS RESTARTS AGE
hello-world-nginx-6dfb89b8bf-8lfsk 1/1 Running 0 19s
hello-world-nginx-6dfb89b8bf-thtpv 1/1 Running 0 19s
hello-world-nginx-6dfb89b8bf-wrb84 1/1 Running 0 19s
One level up, at the deployment level:
$ kubectl get deployments --namespace=hello-world
NAME READY UP-TO-DATE AVAILABLE AGE
hello-world-nginx 3/3 3 3 2m47s
And then the service level, you can see that here:
$ kubectl get services --namespace=hello-world
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-world ClusterIP 10.43.119.12 <none> 80/TCP 83s
If you’d like to tail the logs of all the running nginx pods, looking for your page fetch(es):
$ kubectl logs -f -l app=hello-world --namespace=hello-world
...
2024/09/03 01:33:42 [notice] 1#1: start worker process 36
10.42.0.8 - - [03/Sep/2024:01:33:46 +0000] "GET / HTTP/1.1" 200 86 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0" "10.42.0.1"
...
And this one is probably something you might really want to see – ingress:
$ kubectl get ingress --namespace=hello-world
NAME CLASS HOSTS ADDRESS PORTS AGE
hello-world traefik * 172.16.1.52 80 16m
Note: The address here will be the IP address ⬆️ of your k3s server.
Or just use something like k9s.
Deleting
Essentially we can just go in reverse:
kubectl delete -f hello-world.yml --namespace=hello-world
That should stop and remove the pods and service.
kubectl delete configmap hello-world --namespace=hello-world
That should delete the configmap we created.
kubectl delete namespace hello-world
This ⬆️ will delete the namespace if you want to keep your namespaces clean.
Other Things
It would be nice to develop an example with:
- TLS
- metallb