From my previous post, we flashed a RaspberryPi 4 with RaspbianOS and Installed K3S Distribution of Kubernetes.

In this post we will develop, build and deploy a Basic Golang Web Application and Deploy it to K3S and access our application via Traefik's Reverse Proxy capabilities.

Docker

We will require a container runtime as we will be building a image from our application application code, so in this case we will be installing Docker.

Docker gave some issues installing from the raspbian repositories, so I will be using the get.docker.com source:

$ curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh

To verify that docker has been installed:

$ docker version
Client: Docker Engine - Community
 Version:           19.03.1
 API version:       1.40
 Go version:        go1.12.5
 Git commit:        74b1e89
 Built:             Thu Jul 25 21:33:17 2019
 OS/Arch:           linux/arm
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.1
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.5
  Git commit:       74b1e89
  Built:            Thu Jul 25 21:27:09 2019
  OS/Arch:          linux/arm
  Experimental:     false
 containerd:
  Version:          1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Our Golang Application

We will develop a basic web application that returns the hostname from the container where its running from when making a http request.

Our code, app.go :

package main

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

func hostnameHandler(w http.ResponseWriter, r *http.Request) {
    myhostname, _ := os.Hostname()
    fmt.Fprintln(w, "Hostname:", myhostname)
}

func main() {
    const port string = "8000"
    fmt.Println("Server listening on port", port)
    http.HandleFunc("/", hostnameHandler)
    http.ListenAndServe(":" + port, nil)
}

For our Dockerfile, we will be using a golang alpine image from arm32v7 for our build to compile our binary.

Note that I am compiling it for ARM architecture, if you want to run this on anything else than a RaspberryPi or ARM, then specify the arch of your choice.

Once the binary is built, we will then copy the compiled golang binary to a scratch image.

Our Dockerfile:

FROM arm32v7/golang:alpine AS builder
ADD app.go /go/src/hello/app.go
WORKDIR /go/src/hello
RUN apk add --no-cache gcc libc-dev
RUN GOOS=linux GOARCH=arm GOARM=5 go build app.go

FROM hypriot/rpi-alpine-scratch
COPY --from=builder /go/src/hello/app /app
CMD ["/app"]

Build the image by using the image name and tag of your own account:

$ docker build -t ruanbekker/rpi-hostname:latest .

Now the image has been built, let's test this locally:

$ docker run -it -p 80:8000 ruanbekker/rpi-hostname:latest
Server listening on port 8000

Make a HTTP Request:

$ curl -i http://192.168.0.100/
HTTP/1.1 200 OK
Date: Sat, 10 Aug 2019 13:14:20 GMT
Content-Length: 23
Content-Type: text/plain; charset=utf-8

Hostname: 7c5f0d0bc212

And we can see it works as expected. Push the image to your image repository of choice, I will be using docker hub (make sure to use your own account):

$ docker push ruanbekker/rpi-hostname

Deploying to Kubernetes

Since we have a working application packaged up into a image, its time to deploy our application to Kubernetes.

You can use my image if you did not push to your own repository. Our deployment spec, k3s-demo.yml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: k3s-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k3s-demo
  template:
    metadata:
      labels:
        app: k3s-demo
    spec:
      containers:
      - name: k3s-demo
        image: ruanbekker/rpi-hostname:latest
---
apiVersion: v1
kind: Service
metadata:
  name: k3s-demo
  namespace: default
spec:
  ports:
  - name: http
    targetPort: 8000
    port: 80
  selector:
    app: k3s-demo
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: k3s-demo
  annotations:
    kubernetes.io/ingress.class: "traefik"

spec:
  rules:
  - host: k3s-demo.example.org
    http:
      paths:
      - path: /
        backend:
          serviceName: k3s-demo
          servicePort: http

From the configuration above we are using Traefik for our ingress and mapping port 80 to the k3s-demo service via the dns name k3s-demo.example.org

As I do not own example.org, I will be setting that in my /etc/hosts file:

$ cat /etc/hosts | grep k3s
192.168.0.100 k3s-demo.example.org

Deploy our application:

$ kubectl apply -f k3s-demo.yml
deployment.extensions/k3s-demo created
service/k3s-demo created
ingress.extensions/k3s-demo created

After a minute or so, have a look at the services:

$ kubectl get service
NAMESPACE     NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
default       k3s-demo     ClusterIP      10.43.89.51    <none>          80/TCP                       31s

The deployments:

$ kubectl get deployments
NAMESPACE     NAME       READY   UP-TO-DATE   AVAILABLE   AGE
default       k3s-demo   1/1     1            1           42s

And our pods:

$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
k3s-demo-777b9b7799-68kxg   1/1     Running   0          49s

From the above output we can see that our application is deployed and running in Kubernetes.

Testing our Application

Now that our application is deployed and our hosts file is set, let's test the application:

$ curl k3s-demo.example.org
Hostname: k3s-demo-777b9b7799-68kxg

And as you can see the Traefik accepted the connection and routed to the correct container.

Scaling our Application

As the application returns the hostname, it will return the hostname of the container where the application is running.

So if we scale our application to more replicas, every time a request comes in, it will return the hostname of that container and we should see the served hostname from every request that has been made.

Let's scale our application to 3 replicas:

$ kubectl scale --replicas=3 deployments/k3s-demo
deployment.extensions/k3s-demo scaled

Ensure that the deployment has been scaled and the desired number of pods has been checked in:

$ kubectl get deployments
NAMESPACE     NAME       READY   UP-TO-DATE   AVAILABLE   AGE
default       k3s-demo   3/3     3            3           4m3s

Which is true, then we can look a bit further at the pods, where we should see 3 pods:

$ kubectl get pods
NAMESPACE     NAME                         READY   STATUS      RESTARTS   AGE
default       k3s-demo-777b9b7799-68kxg    1/1     Running     0          4m8s
default       k3s-demo-777b9b7799-hm7hm    1/1     Running     0          39s
default       k3s-demo-777b9b7799-j6p7m    1/1     Running     0          39s

Making 3 HTTP Requests, should show the hostnames that we are seeing from the above output:

$ curl k3s-demo.example.org
Hostname: k3s-demo-777b9b7799-68kxg

$ curl k3s-demo.example.org
Hostname: k3s-demo-777b9b7799-hm7hm

$ curl k3s-demo.example.org
Hostname: k3s-demo-777b9b7799-j6p7m

If you are interested in having more nodes in your cluster you can have a look at this gist: https://gist.github.com/ruanbekker/d999161cde3e440194b3f7cd60d57fc2

That's it for now

Thank You

Thanks for reading. If you would like to check out more of my content, check out my website at ruan.dev or follow me on Twitter @ruanbekker