consul-template icon indicating copy to clipboard operation
consul-template copied to clipboard

Query by tag

Open NoumanSaleem opened this issue 9 years ago • 47 comments

I don't see it mentioned in the docs, so this would be a new feature request {{service "api.*"}}

NoumanSaleem avatar Oct 23 '14 14:10 NoumanSaleem

Hi @NoumanSaleem,

You can query by tag using the .tag syntax described here. It's very similar to what you have suggested:

{{service "release.webapp@east-aws:8000"}}

This is querying Consul for the "webapp" service, with the "release" tag, in the "east-aws" datacenter, using port "8000".

sethvargo avatar Oct 23 '14 14:10 sethvargo

@sethvargo what I'm actually trying to accomplish is querying across all services by tag. In my case, I have multiple services which will be routed to by the same proxy.

users: ["api"]
blogs: ["api"]
memcached: []

Probably not a common use case

NoumanSaleem avatar Oct 23 '14 15:10 NoumanSaleem

@NoumanSaleem ah. We've actually thought about this. In your example, you'll just want two loops:

{{service "users.api"}}
thing{{end}}
{{service "blogs.api"}}
thing{{end}}

Does that make sense?

sethvargo avatar Oct 23 '14 15:10 sethvargo

@sethvargo totally does, but was trying to make it a little dynamic ;) not knowing services ahead of time. I've put together an app in the meantime to watch for changes using the REST api, and write the config how I need it. Will switch to consul-template if tag only support comes in the future :)

thanks!

NoumanSaleem avatar Oct 23 '14 16:10 NoumanSaleem

@armon what are your thoughts here?

sethvargo avatar Oct 23 '14 16:10 sethvargo

This is not currently possible given Consul's API. We index by service, not by tag. It is kind of a strange use case to lookup tags across all services.

armon avatar Oct 23 '14 19:10 armon

I think I understand the problem. We also have the same issue. What we try to accomplish is to get a list of all services from consul - since we don't know the service name yet, we can't make a template.

Typical use case: service1.service.consul service2.service.consul ... serviceN.service.consul

HAproxy config that allows access to all of them by port 80 (proxy layer 7) mostly for humans, human can access for service documentation like: service1.service.consul/docs

frontend http-in
    bind *:80

    acl  acl_consul hdr(host) -i consul.service.consul
    use_backend  backend_consul if acl_consul
    acl  acl_service1 hdr(host) -i service1.service.consul
    use_backend  backend_service1 if acl_service1   
#...itareate it dynamically for every new service...

backend backend_consul    
    balance leastconn
    option httpclose
    server 0_consul2_consul 10.0.0.1:8500 maxconn 32
    server 0_consul3_consul 10.0.0.2:8500 maxconn 32

backend backend_service1 
    balance leastconn
    option httpclose
    server 0_services3_consul 10.0.0.3:1234 maxconn 32
    server 0_services4_consul 10.0.0.4:4567 maxconn 32

#... continue it till serviceN...

And since services it selfs are dynamic we are force to make a "template for a template" which it not a nice soution. It would be much easier to iterate over services, as NoumanSaleem suggested - with reular expression it would be even better.

sielaq avatar Oct 23 '14 19:10 sielaq

I actually think the template for a template is a pretty decent solution for this. Would it help if there was a master "services" entry that just provided the list of all known services? Then you could iterate over that to generate the per-service blocks.

armon avatar Oct 23 '14 23:10 armon

For us - Yes, it would definitely help, script logic would be much simpler. I'm not sure if @NoumanSaleem would also appreciate that.

sielaq avatar Oct 24 '14 05:10 sielaq

Would totally help

NoumanSaleem avatar Oct 24 '14 17:10 NoumanSaleem

@armon that won't work as expected because there's two phases to the compilation of a template - first we get the list of dependencies we need, then we query for them. If we have a master list of services, you wouldn't be able to use those in other queries because they would be dynamically compiled. Does that make sense?

sethvargo avatar Oct 24 '14 17:10 sethvargo

@sethvargo I think it would require template chaining. Basically instance A of consul-template sucks in the list of services and emits a new template that has each service block. Instance B of consul-template reads that input template, and generates the final output for HAProxy/Nginx

armon avatar Oct 24 '14 18:10 armon

How about some kind of byTag template function that accepts a []*Service and returns a map[string][]*Service where the map key is the service tag. This would then allow you to have dynamic configuration by having a known service name and then using the tag as the dynamic identifier.

Sticking with the api example one could have all of your relevant applications register that they provide an api service. Each of these services would then have one or more tags assigned which we can use to identify the type of api that they application is providing. In your consul-template template you could then do something like the following.

{{ range $tag, $services := service "api" | byTag }}
      # {{ $tag }} API end points.
      {{ range $services }}
          {{ .Address }}:{{ .Port }}
      {{ end }}
{{ end }}

williambailey avatar Oct 31 '14 15:10 williambailey

@williambailey the problem with that approach is that Consul Template is a two-pass implementation. The first time it reads the template to figure out what services/keys to watch, then it queries Consul. So having some kind of range/loop in the template is rather difficult with the current implementation. That's why @armon suggested having multiple Consul Template instances - one that queries the master services list and renders a template that is a ctmpl, and then another Consul Template instance that consumes that instance.

sethvargo avatar Oct 31 '14 15:10 sethvargo

@sethvargo - The two-pass implementation remains unaffected with my suggestion. The service function is responsible for figuring out the service dependencies. The byTag function simply takes the result of the service function and populates a map that can be used in the template.

williambailey avatar Oct 31 '14 16:10 williambailey

@NoumanSaleem does the new byTag functionality provide the functionality you need?

sethvargo avatar Nov 03 '14 00:11 sethvargo

@sethvargo, How about iterating over all services (so without knowing service name)? Is it possible already? "byTag" works for me (which is preety cool by the way) but still only for known services.

sielaq avatar Nov 03 '14 21:11 sielaq

@sielaq I haven't added a way to iterate over all services yet, but that's "coming soon"(tm) :smile:

sethvargo avatar Nov 03 '14 22:11 sethvargo

@sethvargo as @sielaq said, it would be perfect if it iterated over all services. That + byTag would work for me. For now, template for a template is still working just fine. A definite step in the right direction! Thanks all.

NoumanSaleem avatar Nov 04 '14 16:11 NoumanSaleem

Hello,

After some research, I do not think providing a services function is currently possible due to an upstream bug in Consul. Once that issue has been resolved, we can revisit this scenario.

sethvargo avatar Nov 04 '14 18:11 sethvargo

I have the same issue described above.

I'm running haproxy to share a bunch of micro services on port 80. Problem is I don't know what they're names are ahead of time.

srobertson avatar Nov 12 '14 22:11 srobertson

Hi @srobertson

As you can see from the conversation and label, this is an upstream bug in Consul and Consul Template cannot query by tag until Consul supports it.

sethvargo avatar Nov 13 '14 16:11 sethvargo

Thanks for the response!

Understood, just wanted you to know the use case described is quite desireable by others.

srobertson avatar Nov 13 '14 17:11 srobertson

AFAICT, this is now a duplicate of #77. the 'byTag' functionality has been provided, all that remains is a mechanism for querying all services, which is requested in #77.

The flags are different, though. This one is marked as "upstream bug" implying we need something from consul before this bug can proceed and #77 is marked as "enhancement" implying we don't need to wait.

bryanlarsen avatar Nov 25 '14 11:11 bryanlarsen

@bryanlarsen it's slightly different. The participants in this issue want to query the services catalog, not the health endpoint.

sethvargo avatar Nov 25 '14 15:11 sethvargo

from version 0.5.0 services was introduced, but I'm not sure if I use it properly. this example gives me "half" empty output of consul service itself (I choosed consul sine everyone have it):

frontend http-in
    bind *:80
    {{range services}}acl acl_{{.Name}} hdr(host) -i {{.Name}}.service.consul
    use_backend backend_{{.Name}} if acl_{{.Name}}
    {{end}}
    {{range services }}backend backend_{{.Name}}
      balance leastconn
      option httpclose
    {{range service .Name "passing" }}  server {{.Name}} {{.Address}}:{{.Port}} maxconn 32
    {{end}}{{end}}

result:

frontend http-in
    bind *:80
    acl acl_consul hdr(host) -i consul.service.consul
    use_backend backend_consul if acl_consul

    backend backend_consul
      balance leastconn
      option httpclose

BUT, if I mention about service before like:

{{ $services := service "consul" }}
frontend http-in
    bind *:80
    {{range services}}acl acl_{{.Name}} hdr(host) -i {{.Name}}.service.consul
    use_backend backend_{{.Name}} if acl_{{.Name}}
    {{end}}
    {{range services }}backend backend_{{.Name}}
      balance leastconn
      option httpclose
    {{range service .Name "passing" }}  server {{.Name}} {{.Address}}:{{.Port}} maxconn 32
    {{end}}{{end}}

suddenly backeds appears:

frontend http-in
    bind *:80
    acl acl_consul hdr(host) -i consul.service.consul
    use_backend backend_consul if acl_consul

    backend backend_consul
      balance leastconn
      option httpclose
      server consul 192.168.59.103:8300 maxconn 32

So is it by design or is this just a first step and this is in your plans, and "patient I must have" :) ?

sielaq avatar Dec 21 '14 14:12 sielaq

@sielaq services is different than service. services returns a list of Catalog services, but service returns a health check service object, which has significantly more information.

This is still an upstream bug in Consul, and sorry for the confusion around service vs services.

sethvargo avatar Dec 21 '14 16:12 sethvargo

@sethvargo , So does it mean that I cannot combine both of those? like:

{{range services}}        # this supposed to iterate over "Catalog Services"
  {{range service .Name}} # this supposed to iterate over "service instances" for every .Name which comes from "Catalog Services"
    {{.Address}}:{{.Port}}
  {{end}}
{{end}}

Because this is what my previous examples do, probbaly indenting is misleading... If I cannot combine both then we missunderstand what "Catalog Services" is :(

sielaq avatar Dec 21 '14 18:12 sielaq

@sielaq no because of #64 :smile:

The different between catalog services and agent services is explained in the Consul API documentation.

sethvargo avatar Dec 21 '14 20:12 sethvargo

@sethvargo , Ok so we understant it properly. Catalog Service - is a global catalog - not a local agent catalog -- exacly what we need. But the combination of both "servieces" and "service" is a logically proper, both of those used "catalog/services" and ~~"catalog/service/"~~ just it is not working yet, due to https://github.com/hashicorp/consul-template/issues/64 ? Or this combination is logically wrong ? (I just wanna clarify - sorry for being picky :) )

EDIT: I read again about "service" and it end point is "health/service/", but still docummentation says "This is very similar to the /v1/catalog/service endpoint however, this endpoint automatically returns the status of the associated health check, as well as any system level health checks."

so my question is still valid, is it logically proper ? I understand that in the end, result of generated config can contain an empty service (where all instances are in "critical" state).

sielaq avatar Dec 21 '14 20:12 sielaq