Consul-template nginx vhost

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;
    }
  }
}
1 Like