How can I read consul SRV records in my go application?
Asked Answered
G

4

9

I am trying to implement consul for service discovery, and I am having trouble with two things: connecting to a custom DNS server, and formatting my net.LookupSRV() request.

Here is what I'm trying to look up from within my go app:

$ dig @127.0.0.1 -p 8600 serviceb.service.consul SRV

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @127.0.0.1 -p 8600 serviceb.service.consul SRV
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4511
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 2
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;serviceb.service.consul.   IN  SRV

;; ANSWER SECTION:
serviceb.service.consul. 0  IN  SRV 1 1 80 az1-serviceb1.node.dc1.consul.
serviceb.service.consul. 0  IN  SRV 1 1 80 az2-serviceb2.node.dc1.consul.

;; ADDITIONAL SECTION:
az1-serviceb1.node.dc1.consul. 0 IN A   10.6.41.22
az2-serviceb2.node.dc1.consul. 0 IN A   10.6.41.20

;; Query time: 6 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Fri May 16 15:09:28 2014
;; MSG SIZE  rcvd: 275

and here is the relevant code. (I know it's wrong, but just so you can see what I'm trying to do)

cname, addrs, err := net.LookupSRV("serviceb", "service", "consul")
log.Printf("%+v %+v %+v", cname, addrs, err)

and the output:

2014/05/16 15:24:31  [] lookup _serviceb._service.consul: no such host

Any help would be appreciated! thanks

Gherardo answered 16/5, 2014 at 15:28 Comment(1)
Here is a blog post I wrote outlining what I came up with: txt.fliglio.com/2014/05/… And a go library prototype I started to solve this problem (using the proposal from my accepted answer) github.com/benschw/consul-clb-goGherardo
O
4

Try to use a more sharp tool such as the github.com/miekg/dns package. Last time I looked at it, it allowed to control virtually every bit of the client-side setup to do DNS resolution.

Oxime answered 16/5, 2014 at 16:21 Comment(3)
I'm playing around with this, but i hoped there was a more baked solution than manually interpreting/parsing a dns response. Any ideas?Gherardo
@benschwartz, why manually? The library I linked to does everything for encoding and sending DNS queries and receiving and parsing responses to them. Going one level deeper in your case is fine because in the common case the DNS client just doesn't care about non-standard DNS server setups: it just asks the system resolver to perform a query for it; that's what the DNS client from the standard library does, and this covers 90% of the use cases.Oxime
Is there a better way to parse the Answers array from a request than to use string tools like split?Gherardo
S
2

Consul does support strict RFC 2782 and lookup can be done using the standard library only:

resolver := &net.Resolver{
    Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        return (&net.Dialer{}).DialContext(ctx, network, "127.0.0.1:8600")
    },
}
_, addrs, err := resolver.LookupSRV(
    context.Background(), "svcname", "tcp", "consul",
)
Sochor answered 13/3, 2020 at 14:6 Comment(0)
M
1

While this doesn't answer your exact question, I find it's an easier way to access service data for greenfield apps.

It's quite easy to call the HTTP API with net/http:

package main
import (
    "fmt"
    "net/http"
    "io/ioutil"
)
func main() {
    resp, _ := http.Get("http://localhost:8500/v1/catalog/service/serviceb")
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Print(string(body))
}

The basics of the HTTP API are documented in the Services Guide.

Malachi answered 14/10, 2014 at 18:35 Comment(0)
T
1

The best way is to use PreparedQueries, as Posted here

import (
    "fmt"

    consulapi "github.com/hashicorp/consul/api"
)

func main() {
    config := consulapi.DefaultConfig()
    consul, err := consulapi.NewClient(config)
    if err != nil {
        fmt.Println(err)
    }

preparedQuery := consul.PreparedQuery()
queryID, _, err := preparedQuery.Create(&consulapi.PreparedQueryDefinition{
    Name: "DnsQuery",
    Service: consulapi.ServiceQuery{
        Service:     "serviceb",
        OnlyPassing: true,
    },
}, &consulapi.WriteOptions{})
if err != nil {
    fmt.Println(err)
}

res, _, _ := preparedQuery.Execute(queryID, &consulapi.QueryOptions{})
for _, node := range res.Nodes {
    fmt.Println(node.Service.Address, node.Service.Port)
}
Tinkling answered 27/6, 2020 at 6:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.