loading...

🐳 Kubernetes for the Modern Developer 🐳

chiefnoah profile image Noah Pederson ・Updated on ・6 min read

In this tutorial I cover setting up a sample project in a local, single-node Kubernetes cluster such as microk8s for local, everyday development without having to build a new image every time code changes.


Prerequisites

  • A working local Kubernetes cluster using microk8s
  • Microk8s docker registry enabled using microk8s.enable registry
  • Docker (included with microk8s or install from here)
  • (Optional) Python 3 and pip installed locally

This tutorial assumes you already have a Kubernetes cluster up and running on your local workstation and have a basic understanding of the CLI and containers. If you do not have a local Kubernetes cluster, please refer to the installation instructions linked above.

Note: NONE of the code or configs in this tutorial are 'production ready'.

If you did not have docker or kubectl previously, alias the commands to avoid having to prefix them with microk8s. with:

sudo snap alias microk8s.docker docker
sudo snap alias microk8s.kubectl kubectl

What is Kubernetes?

Kubernetes is a modern platform for automating the deployment, configuration, and scaling of containers and containerized applications. It's built on top of the Docker platform by default and provides developers and sysadmins with flexible and powerful tools for managing their services at any scale. Including workstation scale. πŸ™‚


First things first!

All configurations and code can be found in this GitHub project:

GitHub logo chiefnoah / kubetest

A simple project for setting up a kubernetes development environment

kubetest

A simple project for setting up a kubernetes development environment

Getting Started πŸƒπŸ’¨

Alright, ready to get started? We need to have an app to run in a container, so let's define a simple app using the Flask framework in Python:

# test_server.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def kube():
    return "Hello Kubernetes!"

This defines a Flask application with a single route, /. Nothing scary here! πŸ‘Ύ

If you'd like to test out the app, install the Flask dependency:

pip install flask

... and run with:

FLASK_APP=test_server.py flask run

Then open http://127.0.0.1:5000/ your browser to see the results.

Let's get Dockerized πŸ“¦

Next up we need to define a Dockerfile for our new web service. I'll assume some level of familiarity with Dockerfiles, so I won't break this one down.

FROM python:3.6.7-alpine3.7

RUN mkdir /app
WORKDIR /app

COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

COPY ./test_server.py /app/test_server.py

ENV FLASK_APP=test_server.py
ENV FLASK_ENV=development

ENTRYPOINT [ "flask", "run", "--host=0.0.0.0" ]

Now we need to build and tag the image with our code in it and push it to the registry. To do this run:

docker build -t localhost:32000/kubetest:v1.0.0 .
docker push localhost:32000/kubetest:v1.0.0

Note: depending on which kubernetes distro you are using, the image name may differ, replace localhost:32000 with whatever the hostname for your local registry is.

You only need to build this image once (unless you add new dependencies), as we'll be injecting modified code into the image later on.

Onto the fun stuff... 😏

So now we have a Docker image with the latest version of our code in it, ready to run in our shiny new cluster. Normally around now you would fire up docker run and call it a day, but we're going to do something better than that.

We need to define two types of resources to get our new image running in the cluster, a Deployment and a Service. These will seem kind of familiar if you've used docker-compose before, as they are conceptually very similar.

The Deployment looks like:

# kubetest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubetest
spec:
  selector:
    matchLabels:
      app: kubetest
  template:
    metadata:
      labels:
        app: kubetest
    spec:
      containers:
      - name: kubetest
        image: localhost:32000/kubetest:v1.0.0
        ports:
        - containerPort: 5000
        imagePullPolicy: Always
...

This defines a basic service using the docker image we just pushed named kubetest. The important bits are in the spec structure, where we define a single container entry named kubetest that uses the image localhost:32000/kubetest:v1.0.0 and exposes port 5000. See! Just like a service in docker-compose. πŸ˜„ You can try creating a pod now with kubectl create -f kubetest.yaml, though you won't be able to access the pod via HTTP, so hold off on doing that just yet.

To make your pod accessible from outside the Kubernetes network, we need to define a Service. This is similar to the -p option in docker runor the ports section in a docker-compose.yaml.

# kubetest.yaml
...
---
kind: Service
apiVersion: v1
metadata:
  name: kubetest
  labels:
    app: kubetest
spec:
  selector:
    app: kubetest
  ports:
  - name: http
    port: 5000
    protocol: TCP
  type: NodePort

Note: I have the Deployment and Service definitions in the same file for simplicity.

Alright, let's go ahead and set this up in our cluster for real now!

noah@kamijou:/opt/kubetest$ kubectl apply -f kubetest.yaml
deployment.apps/kubetest created
service/kubetest created

We can look at the resulting pod using:

noah@kamijou:/opt/kubetest$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
kubetest-574958d5fd-zpvn5   1/1     Running   0          15s

... and the service using:

noah@kamijou:/opt/kubetest$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.152.183.1     <none>        443/TCP          8d
kubetest     NodePort    10.152.183.154   <none>        5000:31065/TCP   20s
noah@kamijou:/opt/kubetest$

Pay special attention to the 5000:31065/TCP section in the service list, that 31065 is the port we'll be using to connect to our web service. Let's do that now:

noah@kamijou:/opt/kubetest$ curl -i localhost:31065
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 18
Server: Werkzeug/0.14.1 Python/3.6.7
Date: Wed, 12 Dec 2018 05:53:10 GMT

Hello Kubernetes!

Awesome! πŸŽŠπŸŽ‰πŸŽˆ

... but wait, I was promised cookies being able to edit my code within the pod! 😣 How am I supposed to do that!?

Calm down, we'll get to that! πŸ˜…

Let's go back to our kubetest.yaml file and add in a few lines.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubetest
spec:
  selector:
    matchLabels:
      app: kubetest
  template:
    metadata:
      labels:
        app: kubetest
    spec:
      containers:
      - name: kubetest
        image: localhost:32000/kubetest:v1.0.0
        ports:
        - containerPort: 5000
        imagePullPolicy: Always
+       volumeMounts:
+         - mountPath: /app
+           name: kubetest-volume
+           readOnly: true
+     volumes:
+     - name: kubetest-volume
+       hostPath:
+         path: /home/noah/kubetest
+         type: Directory

There's a bit to unpack here, but the gist of it is we're creating a directory-based folder mount similar to the -v /host/path:/container/path syntax in docker run. Kubernetes handles this slightly different than vanilla docker, so if you'd like to read more on it check out the official documentation on persistent volumes.

Note: change the value for the path section to point to whatever your test_server.py file lives. That's super important for this to work right.

Go ahead and run kubectl apply -f kubetest.yaml again to update our container with the volume mount.

noah@kamijou:~/kubetest$ kubectl apply -f kubetest.yaml
deployment.apps/kubetest configured
service/kubetest unchanged

Check to make sure the pod is up and running again with

noah@kamijou:~/kubetest$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
kubetest-58f7b7d957-cvdch   1/1     Running   0          63s

Let's take a look at the logs, these are slurped up from the STDOUT and STDERR of all pods. We only care about the kubetest service, so lets check that out:

noah@kamijou:~/kubetest$ kubectl logs svc/kubetest
 * Serving Flask app "test_server.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 124-402-077

Cool, app's still running.

Finally, let's add a new endpoint to the test_server.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def kube():
    return "Hello Kubernetes!\n"

+@app.route("/healthz")
+def health():
+    return "ok\n", 200

... check the logs again.

noah@kamijou:~/kubetest$ kubectl logs svc/kubetest
 * Serving Flask app "test_server.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 124-402-077
 * Detected change in '/app/test_server.py', reloading
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 124-402-077

Flask picked up that there was a change to the test_server.py file and reloaded automagically!

Let's try to hit our new endpoint, remember to make sure the port didn't change on you with kubectl get svc

noah@kamijou:~/kubetest$ curl -i localhost:30788/healthz
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3
Server: Werkzeug/0.14.1 Python/3.6.7
Date: Wed, 12 Dec 2018 06:25:33 GMT

ok

Woo, we're done!


Thanks for reading!

I hope you found my post helpful! If you have any questions, please drop by in the comments, I'm happy to help.

This is my first post, so any feedback is super welcome. πŸ™‹β€β™‚οΈ

Discussion

pic
Editor guide
Collapse
adikus profile image
Andrej Hoos

Thanks for the post!
I tried to follow along however I realized I didn't have the kubectl command. I'm assuming I'm supposed to be using microk8s.kubectl instead (since I've installed microk8s) - might be a good idea to make that clear in the post.

Collapse
qq516249940 profile image
qq516249940

COPY ./test_server.py /app/test_server.py

+       hostPath:
+         path: /home/noah/kubetest   
when i start a pod in /app dir.is no test_server.py and
in  /home/noah/kubetest    is no test_server.py ,even nothing in /home/noah/kubetest  dir.
Collapse
aronwolf90 profile image
aronwolf90

Great post. It is always a paint to maintain a docker-compose.yml, when one is running Kubernetes on production.

Collapse
zeerorg profile image
Rishabh Gupta

That's a great post for first time!

Collapse
chiefnoah profile image