Containers Homework
At work, we have several “working groups” centred around specific technologies we use. The purpose of these is to have a way for us lowly engineers to have input into our “tech menu” that we work with. There are several software-engineering-focused groups (JVM, Nodejs, etc.) but so far only one that relates to operations in a broad sense: the container working group.
For the past 4 or 5 meetings of this group, it has felt very much like just an update on what we as a business are doing with containers, rather than anything interesting, so the chair of the group took it upon himself to set us some homework to get us all involved in working with containers. The homework task was as follows:
Apologies for the short notice but I need to cancel today’s working group. In lieu of actually having the meeting I’d like to set you all a small piece of homework…! I’d like you to use Kubernetes to display a local web page which displays your name and a picture of you. Extra credit if you pull / create the name as a secret! And we’ll discuss and demo your attempts in the next session.
Now it would be easy enough to just write an HTML page, store it as a secret in Kubernetes, and then mount as /var/www/html/index.html
in an off-the-shelf nginx container. Where is the fun in that? I decided that I would create my own application that would display all the required information (plus more) pulling from environment variables, and put that in a container.
The application
I initially defaulted to writing a quick web page in Express, quickly changing my mind and opting for writing it in Go instead. Full disclosure before you read this. I am not a software engineer of any kind, nor am I trained in CS in any way, shape, or form. I’m most comfortable in Bash and not afraid to admit it.
package main
import (
"net/http"
"html/template"
"os"
)
func main() {
http.HandleFunc("/", Serve)
http.ListenAndServe(":1337", nil)
}
func Serve(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("index.gohtml")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
type Data struct {
Name string
ImgURL string
BannerCol string
BannerTextCol string
}
data := Data{Name: os.Getenv("NAME"), ImgURL: os.Getenv("IMGURL"), BannerCol: os.Getenv("BANNERCOLOUR"), BannerTextCol: os.Getenv("BANNERTEXTCOLOUR")}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
The two interesting bits of this code (at least as far as I’m concerned) is the use of html/template
and also os.Getenv
. The template file is a very simple HTML page designed to display name and picture in a box in the middle of the screen:
<!DOCTYPE html>
<html lang="en">
<head>
<style type="text/css">
html,body{height:100%;width:100%;padding:0;margin:0;font-family:'Helvetica Neue',sans-serif;color:#;}
#container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;}
#contents{width:50%;border-radius:20px;border:solid 1px #ddd;box-shadow: 0px 0px 15px #bbb;text-align:center;}
h1{background-color:#;border-top-left-radius:20px;border-top-right-radius:20px;padding:10px 0px;margin:0;}
</style>
<title>'s Container WG Homework</title>
</head>
<body>
<div id="container">
<div id="contents">
<h1>My name is </h1>
<img src="" alt="'s image">
</div>
</div>
</body>
</html>
Putting it in a container
Once we have a working application, we need to get it running in a immutable container, which was accomplished with the following Dockerfile:
FROM golang:latest
LABEL maintainer="Oliver Leaver-Smith <oliver@leaversmith.com>"
WORKDIR /app
COPY ./app .
RUN go build -o main .
EXPOSE 1337
CMD ["./main"]
Onwards to the Kube cluster
In the application, we make reference to four environment variables for name, image URL, banner colour, and banner text colour. These need setting in our Kubernetes manifest along with a whole host of other configurations. The finished YAML looks like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ols-ccwg-hwk
labels:
app: ols-ccwg-hwk
spec:
replicas: 3
selector:
matchLabels:
app: ols-ccwg-hwk-app
template:
metadata:
labels:
app: ols-ccwg-hwk-app
spec:
containers:
- name: ols-ccwg-hwk
image: docker.<super secret internal artifact store>.io/ols/ols-ccwg-hwk:latest
imagePullPolicy: Always
ports:
- containerPort: 1337
envFrom:
- secretRef:
name: ols-ccwg-hwk-secrets
---
apiVersion: v1
kind: Service
metadata:
name: ols-ccwg-hwk-service
spec:
selector:
app: ols-ccwg-hwk-app
ports:
- protocol: TCP
port: 1337
targetPort: 1337
type: LoadBalancer
---
apiVersion: v1
kind: Secret
metadata:
name: ols-ccwg-hwk-secrets
data:
NAME: b2xz
IMGURL: aHR0cHM6Ly9vbHMud3RmL29scy5wbmc=
BANNERCOLOUR: MzY0ZmM3
BANNERTEXTCOLOUR: ZmZm
The result
Et voila! The page looks like this
Aftermath
Our next meeting is next Monday (2019-08-05), so we will all demo our attempts then. I’m aware I’ve skipped through quite a lot of ground, and assumed quite a lot of knowledge.
Do you have a comment to make on this post? Start a discussion in my public inbox by emailing ~ols/public-inbox@lists.sr.ht. You can see the inbox here.