I recently had to write a template to generate an nginx config similar to the one requested by the original poster. I modified a bit to solve the exact scenario requested here.
I hope someone finds this useful.
## DO NOT EDIT THIS FILE ##
##
## This file is dynamically generated by consul-template.
## Any changes you make to this file will be overwritten.
##
## Sourced from: nginx-domain-template.ctmpl
## Last generated: {{ timestamp }}
{{- /*
Server template.
This template is used to generate the nginx server configuration. It takes a
string (hostname) as input and returns the template as a string.
*/ -}}
{{ define "server-template" }}
server {
server_name {{ . | sprig_replace "_" "." }};
location / {
# Route requests to the upstream named {{ . }}
proxy_pass http://{{ . }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
{{ end -}}
{{- /*
Upstream server flags.
This template is used to render flags for the upstream configuration.
It takes a string (service name) as input and returns the template as a string.
The has default values for several arguments, but also supports using
alternate values if they are defined in Consul's KV store.
*/ -}}
{{- /* Render optional flags for upstream instance */ -}}
{{ define "upstream-server-flags" }}
{{- /* Retrieve configured max_failures if present. Otherwise, default to 3 */ -}}
{{- $max_fails := keyOrDefault (printf "service/%s/nginx/max_fails" .Name) "3" -}}
{{- /* Retrieve configured max_failures if present.Otherwise, default to 30s */ -}}
{{- $fail_timeout := keyOrDefault (printf "service/%s/nginx/fail_timeout" .Name) "30s" -}}
{{- /* Mark the backend as down if the health status is critical */ -}}
{{- $is_down := sprig_ternary " down" "" (eq .Status "critical") -}}
{{- printf "max_fails=%s fail_timeout=%s%s" $max_fails $fail_timeout $is_down -}}
{{ end }}
{{- /*
Upstream template
This template is used to the upstream configuration for the service.
It takes an array of service health objects as input and returns the template
as a string.
*/ -}}
{{- define "upstream-template" -}}
{{- $firstService := (sprig_first . ) -}}
{{- $domainName := index $firstService.ServiceMeta "domainName" }}
### Configuration for {{ $domainName }}
upstream {{ $domainName | sprig_replace "." "_" }} {
zone upstream_{{ $firstService.Name }} 128k;
{{ range $index, $service := . }}
# Node: {{ $service.Node }}
# Instance: {{ printf "%s-%s" $service.ID $service.Name }}
server {{ printf "%s:%d" .Address .Port }} {{ executeTemplate "upstream-server-flags" $service -}};
{{ end }}
}
{{ end -}}
{{ $allowedTags := parseJSON `["requiresProxy"]` -}}
{{- $servicesByDomain := sprig_dict "" "" -}}
{{- /*
Iterate over services in the catalog and build a map of those requiring
exposure through the proxy
*/ -}}
{{- range services -}}
{{- if and (.Name | contains "sidecar" | not) (containsAny $allowedTags .Tags) -}}
{{- /*
Obtain all services for specified .Name, regardless of health status.
Group services by the metadata 'domainName'
*/ -}}
{{- $groupedServices := (service .Name "any" | byMeta "domainName") -}}
{{- /*
Map also contains services not matching this metadata.
Fetch only services with 'domainName' metadata.
*/ -}}
{{- range $domainName, $services := $groupedServices -}}
{{- /* Only process services with 'domainName' metadata */ -}}
{{- if ne $domainName "_no_domainName_" -}}
{{- /* Create array item if this is the first entry in the map */ -}}
{{- if not (sprig_hasKey $servicesByDomain $domainName) -}}
{{- $_ := sprig_set $servicesByDomain $domainName $services -}}
{{- else -}}
{{- /* Append services to the existing list for this domain */ -}}
{{- $currentList := index $servicesByDomain $domainName -}}
{{- $newList := sprig_concat $currentList $services -}}
{{- $_ := sprig_set $servicesByDomain $domainName $newList -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* Filter empty value that was used to initially create the dictionary */ -}}
{{- $filteredServices := sprig_omit $servicesByDomain "" }}
http {
server_names_hash_bucket_size 64;
{{- /* Render templates for each domain */ -}}
{{- range $domainName, $services := $filteredServices }}
{{ executeTemplate "upstream-template" $services | indent 2 -}}
{{- executeTemplate "server-template" $domainName | indent 2 -}}
{{- end -}}
}
Here’s the resultant configuration file.
## DO NOT EDIT THIS FILE ##
##
## This file is dynamically generated by consul-template.
## Any changes you make to this file will be overwritten.
##
## Sourced from: nginx-domain-template.ctmpl
## Last generated: 2022-03-23T16:34:52Z
http {
server_names_hash_bucket_size 64;
### Configuration for api.example.com
upstream api_example_com {
zone upstream_api 128k;
# Node: b1000.local
# Instance: api-api
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
# Node: b1000.local
# Instance: api2-api2
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s down;
}
server {
server_name api.example.com;
location / {
# Route requests to the upstream named api_example_com
proxy_pass http://api_example_com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
### Configuration for example.com
upstream example_com {
zone upstream_web 128k;
# Node: b1000.local
# Instance: web-web
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
server {
server_name example.com;
location / {
# Route requests to the upstream named example_com
proxy_pass http://example_com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}