So New Job use K8s. I've managed to avoid it thus far, so I only have a vague understanding of how it works. What's the best way to learn a new tool? Come up with a project you can use it on of course, and the best candidate I have right now is the (never ending) energy monitor project, so here we go.
The plan for today is to deploy a tiny API on my home server using k8s, using locally built images (and no docker registry). The aim is to get the hang of how to deploy stuff, not to build anything proper. It could not get more basic than this!
Step 1 - Create the API
First thing's first, create a tiny API. We'll need a total of 3 files and 1 folder:
k8s_experiments/
Dockerfile
app/
main.py
requirements.txt
main.py
contains the Hello World example API copied from the FastAPI docsrequirements.txt
only hasfastapi
anduvicorn
, whatever the latest versions are right nowDockerfile
is a very basic FastAPI runner, again copied directly from the FastAPI docs
Once these were created, we can build the docker image and test it all works as expected before we do the complicated stuff:
- Build the image with this:
sudo docker build . -t hello-web:local
- The tag can be whatever you like, just don't use
latest
because images tagged with this are not cached so can't be used locally. I like descriptive names solocal
is good for me.
- The tag can be whatever you like, just don't use
- Run with this:
sudo docker run --name hello-web -p 80:80 hello-web
-
Test in a browser: just
http://0.0.0.0
in my example
Step 2 - Get it into K8s
According to the microk8s docs: The image we created is known to Docker. However, Kubernetes is not aware of the newly built image. This is because your local Docker daemon is not part of the MicroK8s Kubernetes cluster. We can export the built image from the local Docker daemon and “inject” it into the MicroK8s image cache
To do that we...
* Compress the docker image: sudo docker save hello-web > hello-web.tar
* Inject the compressed image into the k8s cache: microk8s ctr image import hello-web.tar
OK I lied earlier, we do have to create one more file - a deployment definition file. I created it in the root folder which may or may not be a good place for it. I'm sure if it's wrong I'll figure it out soon enough.
-
Create a new file:
hello-web-deployment.yaml
with the following contents::::yaml apiVersion: apps/v1 kind: Deployment metadata: name: hellow-deployment labels: app: hellow spec: selector: matchLabels: app: hellow template: metadata: labels: app: hellow spec: containers: - name: hello-web image: hello-web:local imagePullPolicy: Never ports: - containerPort: 80
The important bits here are image: hello-web:local
which references my locally built image and imagePullPolicy: Never
which will prevent k8s from even trying to get the image from a registry. Ports also tripped me up here, I found containerPort: 80
had to match the port used in the Dockerfile, but that could just be luck and me not understanding something more complicated about port mapping. Again, I'm sure I'll find out as soon as I try doing something more interesting!
Now we are ready to:
* Create a deployment: microk8s kubectl apply -f hello-web-deployment.yaml
* Check the pod status (should be 'Running'): microk8s.kubectl get pods
* Expose the API via a Node Port (don't know what that is): microk8s.kubectl expose deployment hellow-deployment --type=NodePort --name=hello-service
* List the running k8s services to find the port the API is running on: microk8s.kubectl get svc
, which in my case gave me this:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 3h5m
hello-service NodePort 10.152.183.177 <none> 80:30108/TCP 69m
-
Check in a browser, in my case
http://192.168.1.156:30108/
and wahay! Hello World!
Bonus Extras
Useful commands
- list all docker images
docker ps -a
- remove all docker images
docker rm <image id>
- list all k8s deployments
sudo microk8s.kubectl get deployments
- list all k8s pods and their status
microk8s.kubectl get pods
- list k8s services
microk8s.kubectl get svc
Problems
- If you get the status
ImagePullBackOff
when you list the pods it means k8s can't find your image, I solved it with that imagePullPolicy in the yaml file but there's probably loads of ways to cause it - Ports. I'm not sure where I was going wrong here, but by default I never run anything on 80 (or other commonly used ports), I like to pick some random number usually in the 5000s. But whatever I did with this it wasn't having it, I could only make it work by setting everything to 80. I guess it doesn't matter what FastAPI is working with inside the container because it's mapped outside but how does that work - does it map to something in Docker then again in k8s?
Credits
Most of these instructions were copied from this medium post with some tweaks from the k8s docs