Home brewn service discovery with Go
Introduction
I have a bunch of daemons running on my raspberry pi such as redis and postgres. I want to connect whatever project I am working on locally on my mac to them as dependencies but I have to use the IP of the raspberry pi. It is not a huge deal because my raspberry pi has a static IP but it would be nice if I could use hostname like redis.xyz
or postgres.xyz
and have it use my raspberry pi automatically. I could update my /etc/hosts
file but where is the fun in that?
Thankfully that is possible. Being able to use a human friendly hostname to map to an IP is simply DNS. I can create a DNS server that exposes a write API to map hostnames to IP, which are then used for the read DNS queries.
Dependencies
There are two main parts to this app. The DNS server for the reads and a REST endpoint for the writes. The DNS server will be handled by Miekg’s DNS packagegithub.com/miekg/dns
and the rest endpoint by github.com/labstack/echo
Show me the code
State
We will hold our hostname to IP mappings in a map[string]string
- this could be in memory, in an embedded k/v store such as badgerDB, or plain old sqlite.
DNS Query
The signature for the method that will handle the DNS queries will be func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg)
, similar to a regular net/http handler. I am only dealing with A records so this service only responds to A record requests. To parse the query that came in we can use the following function
1 | func parseQuery(m *dns.Msg) { |
And our handler that invokes the parse will be
1 | func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { |
To wire the server up our main will be
1 | // attach request handler func |
We can use dig against the server to see if it is working dig @localhost -p 5353 foo.xyz
The full gist can be found here
Write endpoint
To allow entries to get into our state we can expose a write rest endpoint that will take in a hostname and an IP to register it against.
1 | POST /register |
For that we can leverage Echo a simple and performant http router.
1 | struct RegisterRequest { |
Now we can hit the register whatever domains we want to register against whatever IPs and point our local machine to use this as its dns server and we are good to go.
Conclusion
This is what service discovery is. Provide a way to register and unregister endpoints and allow for reads via DNS. That is what consul does at the end of the day. DNS is also convinient compared to an application level service lookup because it works at the system level - so things like nginx and curl also ‘just work’.