gomongo part 1

golang microservice on mongo in docker

Environment used in this example:

  • host: MacOS 10.10.5
  • coreOS: alpha (1000.0.0)
  • Docker: version 1.10.3
  • docker-compose: version 1.6.2

End-to-end example: part1

of simple microservice written in golang, reading data from mongodb, packed into docker and linked used docker-compose.

In this series we will be building a very simple http REST interface on top of a small, filled with geo-data mongodb. You will see how to start thinking about things like automated deployment and continuous delivery in early stages of development. We will also look into docker-compose and how can it help us provide easy integration test sandbox.

For the impatient ones - the final code of these series is available here

git clone git@github.com:envimate/gomongo.git

I will be using tags to indicate step-by-step evolvement of the code.

We will go step by step, building up to an http server serving various requests towards mongodb instance. We’ll package that all into docker. We will run our simple http-call-tests towards a dockerized mongo instance, using docker-compose to describe and run our service together with mongodb instance.

As test data we will use geo data which was put together using http://www.naturalearthdata.com/downloads/ and https://www.maxmind.com/en/open-source-data-and-api-for-ip-geolocation and it is available under the repository.

golang http server

For the first part we will setup the initial project structure and start with a simple http server.

the code of the first part is: https://github.com/envimate/gomongo/tree/part1

git checkout tags/part1 -b part1

the project structure looks like this


mongo-go nisabek$ tree  .
.
├── mongo-data
│   ├── countrypopulator.js
│   └── dump
│       └── geo
│           ├── city.bson
│           ├── city.metadata.json
│           ├── country.bson
│           ├── country.metadata.json
│           ├── region.bson
│           ├── region.metadata.json
│           └── system.indexes.bson
└── src
    └── gomongo
        └── gomongo.go

5 directories, 10 files

mongo-data directory contains data files, which we will need to populate our database for. The code for http server is in gomongo.go


package main

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

var port int

func init() {
      flag.IntVar(&port, "port", 8080, "Port on which to listen")
      flag.Parse()
}
func main() {
      log.Println("Starting server on port", port)
      s := &http.Server {
             Addr: fmt.Sprintf(":%d", port),
             Handler: &MongoHandler{},
      }

      log.Fatal(s.ListenAndServe())
}

type MongoHandler struct {
}

func (mhandler *MongoHandler) ServeHTTP(rw http.ResponseWriter, request *http.Request) {
      log.Println("got request", request.RequestURI)
}

let’s quickly go through what it does:

  • reading port parameter from command line - taking 8080 as default
  • initializing Server object with the mentioned port, and MongoHandler instance as Handler
  • Listening and Serving on the server object we created

So what is this Handler field? We need an instance of Handler interface which means having a struct, which implements the required ServeHTTP method should be more than enough to get us going. And that’s exactly what we have - a sample implementation of the method, printing out the requestURI.

building

But let us get one step back here and realize that we have already built a working microservice. Although it’s just a few lines of code, and it doesn’t do anything complicated, it is still an HTTP service, that is capable of serving requests. So it’s about the time to start thinking how we can ship our service. Let’s add an initial build.sh to the project to test what it does and eventually start shipping it.

you can checkout the version with build script here

git checkout tags/part1_build_script -b part1_build_script

the build script looks like this so far:


mongo-go nisabek$ cat build.sh
#!/bin/bash
export GOPATH=`pwd`

go install -v gomongo

and upon running will produce an executable in the bin ( folder (which I pre-created, and excluded from git)

Let’s run the service now


mongo-go nisabek$ ./bin/gomongo
2016/03/06 19:59:45 Starting server on port 8080

If I now make requests with some path to my service, it will do nothing more than just printing the requests

nisabek$ curl http://localhost:8080/test

on the server side we see

2016/03/06 20:00:44 got request /test

dockerizing

Now, we are aiming to have our service run in a docker instance. We have a working binary, so why not start writing the Dockerfile representing our service already.

I prefer working on docker files from my coreOS instance. This is just a matter of preference, if you have docker up and running on your OS, be that mac, windows, or linux, you can feel to use that. coreOS gives me a separated environment where I can play around with docker services. I have mentioned getting started with it in the docker-networking series.

The only thing I need is to add my gomongo project folder to shared section in my config.rb so that I can access it from the coreOS box.

coreos-vagrant nisabek$ cat config.rb | grep shared
$shared_folders = {'/Users/nisabek/mongo-go/' => '/home/core/mongo-go'}

Next, entering my vagrant instance, I’ll start writing my Dockerfile.

I prefer keeping the docker-related content in a separate folder, so I have created a docker folder, where I’ll be keeping all docker-related scripts and files.

As the base image we’ll use the official golang image which is nothing more than Debian image packed with golang tools, env variables set up, etc.

FROM golang

ADD src/gomongo /go/src/gomongo/
ADD build.sh /go
RUN /go/build.sh

ENTRYPOINT /go/bin/gomongo

EXPOSE 8080

Let’s have a look what it does

  • inheriting from golang
  • adding the sources of our app to the /go/src/ directory on the container
  • adding the build script to the root directory
  • building the application
  • setting the entrypoint to the newly built executable
  • exposing the 8080 port

You can check out the version with initial docker file here

 git checkout tags/docker_initial -b docker_initial

To build our image I need to run the docker build command from the project root directory so that the source files are properly located, so I need to explicitly specify the Dockerfile path. I’ll also name the image so that we can manipulate it easily.

docker build -f docker/Dockerfile -t "gomongo" .

This will build the image with the name gomongo. To see the result we can run docker images and see something like this

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
gomongo             latest              b0565fe350c9        10 seconds ago      751.7 MB

Now we can run our image, and map any host port of choice to the exposed port.

docker run -d --name gomongo_container -p 8081:8080 gomongo

running docker ps will show us that the container was successfully started docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                    NAMES
6a5b650ea843        gomongo             "/bin/sh -c /go/bin/g"   About a minute ago   Up About a minute   0.0.0.0:8081->8080/tcp   gomongo_container

Let’s do an http call now, to make sure our initial handler is logging properly

core@core-01 ~/mongo-go $ curl http://localhost:8081/test

And looking into the container logs

core@core-01 ~/mongo-go $ docker logs -f gomongo_container
2016/03/12 20:35:26 Starting server on port 8080
2016/03/12 20:35:47 got request /test

As you can see, we now have a small http service written in go, being delivered easily in a docker instance. We now can go and develop the service further without worrying about deployment much.

In the second part (comming soon :) ) we will see how to connect our http service to mongodb and start up dockerized application using docker-compose.

More Reading