Notes on Building a Raspberry Pi Kubernetes Cluster (Part 5: Building and Running A First App)

For a full list of posts in this series, see Notes on Building a Raspberry Pi Kubernetes Cluster.

With an operational Kubernetes cluster, it’s time to run something! We’ll start with the very simple Go web server that’s listed below (save it as as tiny.go):

import (
"fmt"
"log"
"net/http"
)

You can run this locally with go run tiny.go. To test it, open a separate terminal and use curl:

% curl localhost:8080
Hi there, I really do love kubernetes!

Running this in Kubernetes takes a little more effort. Here’s a summary of what we need to do:

  1. Build the app into a container image.
  2. Push the container image to a container registry (we’ll use DockerHub).
  3. Deploy the container in our Kubernetes cluster.
  4. Expose the deployment as a service.
  5. Add an ingress to allow external connections to our service.

Build the app into a container image.

To run this in Kubernetes, we first need to build it into a container image. We’ll use docker for this.

Here’s aDockerfile for building the container image:

FROM golang:1.16.5-buster as builder
WORKDIR /app
COPY tiny.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -v -o tiny tiny.go

Notice that this does a two-stage build. First it uses a standard Go image to build the app, then it copies it into an Alpine container to keep the image size small.

Build your image with the following, which assigns the tiny tag to the image:

docker build -t tiny .

Now you can run the image locally with Docker:

docker run -p 8080:8080 tiny

The -p 8080:8080 option makes port 8080 inside the container correspond to port 8080 on your local system, so you can verify that your is running with the samecurl commands that you used earlier.

Push the container image to DockerHub.

To use your container in Kubernetes, you’ll need to store it in a container registry that Kubernetes can access. Microk8s supports a local registry, but it’s easier to use DockerHub (which is also less likely to fail and lose your container images). You’ll need a Dockerhub account (it’s currently free), and after you’ve created your account and logged your local docker in using docker login, you can build and push your images to DockerHub (be sure to replace DOCKERHUBID with your own DockerHub id):

docker build -t DOCKERHUBID/tiny .
docker push DOCKERHUBID/tiny

Note that you can also run this version locally:

docker run -p 8080:8080 DOCKERHUBID/tiny

If you’re curious to see what’s in the image that you just pushed to DockerHub, see What’s in a Docker Image?

Deploy the container in our Kubernetes cluster.

To run your container in Kubernetes, you’ll create a deployment. You can do this by applying a YAML file like the following (name it tiny.yaml and replace DOCKERHUBID with your own DockerHub id):

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: tiny
name: tiny
spec:
selector:
matchLabels:
run: tiny
template:
metadata:
labels:
run: tiny
spec:
containers:
- name: tiny
image: DOCKERHUBID/tiny:latest
ports:
- containerPort: 8080
nodeSelector:
kubernetes.io/arch: arm64

Create the deployment with kubectl apply -f tiny.yaml. You can check the status of your deployment with kubectl describe deployments/tiny. You can also access your deployment from one of the nodes in your cluster. To do so, you’ll need the virtual address of the pod running your tiny server. First use kubectl get pods to get the full name of your tiny pod, then use kubectl describe pods/tiny-xxxxxxxxxx-xxxxx (with x’s replaced by your actual id). This will display a YAML file with an IP field. Use the value of this field to curl your service. Your curl command will look like this:

curl 10.1.112.169:8080

Expose the deployment as a service.

To make your deployment available outside of your cluster, you need to take two more steps. First, expose the deployment as a service. The following command sets up a mapping from port 80 of the new service to port 8080 of your container:

kubectl expose deployment tiny --port 80 --target-port 8080

Add an ingress.

Next we need to add an ingress. We’ll also use this to set up HTTPS. Let’s put our ingress definition in a file called http-ingress.yaml, replacing a.domain.me with a domain that you want to use for your tiny service (this should be one of the domains that you set up in Part 4).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: http-ingress
spec:
tls:
- hosts:
- a.domain.me
secretName: home-tls
rules:
- host: a.domain.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tiny
port:
number: 80

Create the ingress with kubectl apply -f ingress.yaml.

Note that it refers to a secret called home-tls. Since you’re creating this ingress in your default namespace, the home-tls secret should also be defined there. Create it using the command for creating secrets in Part 4, but this time, omit the --namespace option:

microk8s kubectl create secret tls home-tls \
--cert=fullchain.pem \
--key=privkey.pem

Now you should be able to connect to your tiny server using HTTPS and the domain that you assigned. Test it with:

% curl https://a.domain.me
Hi there, I really do love kubernetes!

Now you’re running your first web service in Kubernetes! To add another, just go back through the steps above, creating a new container image, deployment, and service, and then modify your ingress to include the new endpoint.

Software developer in the SF Bay area. Electronic Design Automation, iOS apps, and now API tools for the world’s largest computer.