Building a ToDo API with Golang and Kubernetes! – Part 3 – Building and Deploying our Application

In Part 1 and Part 2 we built a functional API using Golang and MongoDB, although as it stands its insecure. We can add password protection using Basic Auth while deploying (See here). In this tutorial we will build a very tiny docker container and deploy to our Kubernetes Cluster.

For the purposes of this tutorial I won’t be showing you how to setup a Kubernetes Cluster, However you can do it easily with Terraform and DigitalOcean.

Assumptions Made

  • You have a functional Kubernetes Cluster
    • GKE – Google Kubernetes Engine
    • AKE – Azure Kubernetes Engine
    • EKS – Elastic Kubernetes Engine (AWS)
    • Rolled your own
  • You have a working nginx ingress controller
  • You have a working loadbalancer for your cluster
  • You have Docker running on your machine
  • You have a dockerhub account

Accepting Environment Variables

As we will be deploying this to a cluster with an external MongoDB instance we need to accept an environment variable for MongoDB.

in our main.go file replace

var session, _ = mgo.Dial("127.0.0.1")

with

var session, _ = mgo.Dial(os.Getenv("MONGO_HOST"))

Remember to add the os import

Building our Container

Compiling the Binary

As I mentioned we will be building a very minimal docker container for our API. This is acomplished by building our go binary using CGO and GOOS environment variables. This will build a statically linked binary for the GOOS we choose, For this scenario it will be Linux.

So in our project directory lets build our binary. To make it easier and repeatable I have created a Makefile

all: main.go
 CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o bin/todo-api .

now run make and you will have an ELF binary in bin/todo-api

You can run this however it will fail to connect unless you specify a valid MongoDB host using the MONGO_HOST environment variable.

Building the Container

In order to make our container as small as possible we will be using the scratch base image from Docker. This image literally has nothing in it, This will allow us to add our binary and nothing else. So lets write our Dockerfile

FROM scratch

LABEL authors="Keiran Smith <opensource@keiran.scot>"

ADD bin/todo-api todo-api

EXPOSE 8000

CMD ["/todo-api"]

Now we have the Dockerfile we can run

$ docker built -t affixxx/todo-api:latest .

remember to tag it with your own dockerhub username!

And thats it we now have a super small Docker container mine is 7.52MB

You can run it but remember to specify the MONGO_HOST environment variable.

Lets push to dockerhub

$ docker push affixxx/todo-api

And thats all there is to it. We can now look at deploying our application. Feel free to deploy mine

Deploying to Kubernetes

Before we start I recommend we create a namespace for our project.

$ kubectl create ns todo-api

Having our project in a seperate namespace ensures that we won’t affect anything else on our kubernetes cluster.

Deploying MongoDB

Our MongoDB deployment has 2 parts, A Service and a Deployment. Setting up Persistent Volumes is beyond scope and I will write a tutorial in the future on working with PVs in kubernetes.


apiVersion: v1
kind: Service
metadata:
  name: todo-api-mongodb
  labels:
    app: todo-api
spec:
  ports:
    - port: 27017
  selector:
    app: todo-api
    tier: mongodb
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb
  labels:
    app: todo-api
spec:
  selector:
    matchLabels:
      app: todo-api
      tier: mongodb
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: todo-api
        tier: mongodb
    spec:
      containers:
      - image: mongo
        name: mongodb
        ports:
        - containerPort: 27017
          name: mongodb

Deploying the ToDo API

Our API only needs a Service and a Deployment. As the application itself is an entirely stateless application.

apiVersion: v1
kind: Service
metadata:
  name: todo-api
  labels:
    app: todo-api
spec:
  ports:
    - port: 8000
  selector:
    app: todo-api
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-api
  labels:
    app: todo-api
spec:
  selector:
    matchLabels:
      app: todo-api
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: todo-api
    spec:
      containers:
      - image: affixxx/todo-api:latest
        name: todo-api
        imagePullPolicy: Always
        env:
        - name: MONGO_HOST
          value: todo-api-mongodb
        ports:
        - containerPort: 8000
          name: todo-api

Writing our Ingress

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  namespace: application
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: todo.kube.kfj.io # Valid FQDN
    http:
      paths:
      - path: /
        backend:
          serviceName: todo-api
          servicePort: 8000

Now we have written our application lets deploy it

$ kubectl apply --namespace todo-api -f mongodb.yml
service "todo-api-mongodb" created
persistentvolumeclaim "mongodb-pv-claim" created
deployment "mongodb" created
$ kubectl --namespace todo-api apply -f todo-api.yml
service "todo-api" configured
deployment "todo-api" created
$ kubectl --namespace todo-api apply -f ingress.yml
ingress "ingress" created

Testing the API

Create

$ curl -X POST -d "description=A Todo Item" todo.kube.kfj.io/todo
{"ID":"5adbba7fc876a1000189f705","Date":"2018-04-21T22:26:07.228Z","Description":"A Todo Item","Done":false}

Read

Multiple

$ curl todo.kube.kfj.io/todo
[
  {"ID":"5adbba7fc876a1000189f705","Date":"2018-04-21T22:26:07.228Z","Description":"A Todo Item","Done":false},
  {"ID":"5adbbaf4c876a1000189f706","Date":"2018-04-21T22:28:04.123Z","Description":"Deploy to Kubernetes","Done":false}
]

Single

$ curl todo.kube.kfj.io/todo/5adbbaf4c876a1000189f706
[{"ID":"5adbbaf4c876a1000189f706","Date":"2018-04-21T22:28:04.123Z","Description":"Deploy to Kubernetes","Done":false}]

Update

$ curl -X PATCH todo.kube.kfj.io/todo/5adbbaf4c876a1000189f706
{"updated": true}
$ curl todo.kube.kfj.io/todo/5adbbaf4c876a1000189f706
[{"ID":"5adbbaf4c876a1000189f706","Date":"2018-04-21T22:28:04.123Z","Description":"Deploy to Kubernetes","Done":true}]

Delete

$ curl -X DELETE todo.kube.kfj.io/todo/5adbba7fc876a1000189f705
{result: 'OK'}

SUCCESS!

Thats it our API is deployed and working.

Thanks for coming back for part 3 of this mini series. Today you have learned to build super small docker images and deploy them to a kubernetes cluster. I hope you found it useful!

I will leave my API deployed to http://todo.kube.kfj.io and leave it open for a while if people want to play with it.

Thanks for reading.

3 responses to “Building a ToDo API with Golang and Kubernetes! – Part 3 – Building and Deploying our Application

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.