From 0c07fc560a55bc0443fc613aab43cf8058d2d618 Mon Sep 17 00:00:00 2001 From: BobSilent Date: Wed, 4 Mar 2020 00:09:24 +0100 Subject: [PATCH] Support exposing multiple ports --- README.md | 2 + nginx.tmpl | 137 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 80 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index b5e0825..369b46a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ If your container exposes multiple ports, nginx-proxy will default to the servic [1]: https://github.com/jwilder/docker-gen [2]: http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/ +`VIRTUAL_PORT` supports a comma seperated list of key value pair syntax `hostPort:containerPort` or `containerPort` only. E.g. `VIRTUAL_PORT=443:80,3000,4000:8086`, this will expose containter port 80 to host port 443, 3000 to 3000 and container port 8086 to host port 4000. + ### Multiple Hosts If you need to support multiple virtual hosts for a container, you can separate each entry with commas. For example, `foo.bar.com,baz.bar.com,bar.com` and each host will be setup the same. diff --git a/nginx.tmpl b/nginx.tmpl index ae9639b..7f685b3 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -1,25 +1,25 @@ {{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }} -{{ $external_http_port := coalesce $.Env.HTTP_PORT "80" }} -{{ $external_https_port := coalesce $.Env.HTTPS_PORT "443" }} +{{ $default_external_http_port := coalesce $.Env.HTTP_PORT "80" }} +{{ $default_external_https_port := coalesce $.Env.HTTPS_PORT "443" }} {{ define "upstream" }} {{ if .Address }} {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} {{ if and .Container.Node.ID .Address.HostPort }} - # {{ .Container.Node.Name }}/{{ .Container.Name }} - server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; + # {{ .Container.Node.Name }}/{{ .Container.Name }} + server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} {{ else if .Network }} - # {{ .Container.Name }} - server {{ .Network.IP }}:{{ .Address.Port }}; + # {{ .Container.Name }} + server {{ .Network.IP }}:{{ .Address.Port }}; {{ end }} {{ else if .Network }} - # {{ .Container.Name }} + # {{ .Container.Name }} {{ if .Network.IP }} - server {{ .Network.IP }} down; + server {{ .Network.IP }} down; {{ else }} - server 127.0.0.1 down; + server 127.0.0.1 down; {{ end }} {{ end }} @@ -143,9 +143,9 @@ proxy_set_header Proxy ""; {{ $enable_ipv6 := eq (or ($.Env.ENABLE_IPV6) "") "true" }} server { server_name _; # This is just an invalid value which will never trigger on a real hostname. - listen {{ $external_http_port }}; + listen {{ $default_external_http_port }}; {{ if $enable_ipv6 }} - listen [::]:{{ $external_http_port }}; + listen [::]:{{ $default_external_http_port }}; {{ end }} {{ $access_log }} return 503; @@ -154,9 +154,9 @@ server { {{ if (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} server { server_name _; # This is just an invalid value which will never trigger on a real hostname. - listen {{ $external_https_port }} ssl http2; + listen {{ $default_external_https_port }} ssl http2; {{ if $enable_ipv6 }} - listen [::]:{{ $external_https_port }} ssl http2; + listen [::]:{{ $default_external_https_port }} ssl http2; {{ end }} {{ $access_log }} return 503; @@ -172,37 +172,6 @@ server { {{ $host := trim $host }} {{ $is_regexp := hasPrefix "~" $host }} -{{ $upstream_name := when $is_regexp (sha1 $host) $host }} - -# {{ $host }} -upstream {{ $upstream_name }} { - -{{ range $container := $containers }} - {{ $addrLen := len $container.Addresses }} - - {{ range $knownNetwork := $CurrentContainer.Networks }} - {{ range $containerNetwork := $container.Networks }} - {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} - ## Can be connected with "{{ $containerNetwork.Name }}" network - - {{/* If only 1 port exposed, use that */}} - {{ if eq $addrLen 1 }} - {{ $address := index $container.Addresses 0 }} - {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} - {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}} - {{ else }} - {{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }} - {{ $address := where $container.Addresses "Port" $port | first }} - {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} - {{ end }} - {{ else }} - # Cannot connect to network of this container - server 127.0.0.1 down; - {{ end }} - {{ end }} - {{ end }} -{{ end }} -} {{ $default_host := or ($.Env.DEFAULT_HOST) "" }} {{ $default_server := index (dict $host "" $default_host "default_server") $host }} @@ -241,17 +210,65 @@ upstream {{ $upstream_name }} { {{ $is_https := (and (ne $https_method "nohttps") (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert))) }} +{{/* Define an upstream name either by host or if is regex by sha1 */}} +{{ $upstream_name := when $is_regexp (sha1 $host) $host }} + +{{/* Set a variablename for mapping host:port to an upstream name */}} +{{ $proxypass_variable := printf "%s%s" "$upstream_" (replace $upstream_name "." "_" -1) }} + +{{ $default_port := when (or (not $is_https) (eq $https_method "noredirect")) $default_external_http_port $default_external_https_port }} + +# {{ $host }} +{{ $hostPortContainersMap := groupByMultiKeyValuePairs $containers "Env.VIRTUAL_PORT" "," ":" $default_port }} +map $host:$server_port {{ $proxypass_variable }} { + {{ range $port, $containers := $hostPortContainersMap }} + {{ $host }}:{{ $port }} {{ $upstream_name }}_{{ $port }}; + {{ end }} +} + +{{ range $port, $portContainers := $hostPortContainersMap }} +upstream {{ $upstream_name }}_{{ $port }} { + {{ range $container := $portContainers }} + {{ $portHostPortContainerMap := splitKeyValuePairs $container.Env.VIRTUAL_PORT "," ":" $default_port }} + {{ $addrLen := len $container.Addresses }} + + {{ range $knownNetwork := $CurrentContainer.Networks }} + {{ range $containerNetwork := $container.Networks }} + {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} + ## Can be connected with "{{ $containerNetwork.Name }}" network + + {{/* If only 1 port exposed, use that */}} + {{ if eq $addrLen 1 }} + {{ $address := index $container.Addresses 0 }} + {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} + {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}} + {{ else }} + {{ $containerPort := coalesce (index $portHostPortContainerMap $port) "80" }} + {{ $address := where $container.Addresses "Port" $containerPort | first }} + {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} + {{ end }} + {{ else }} + # Cannot connect to network of this container knownNetwork={{ $knownNetwork.Name }} containerNetwork={{ $containerNetwork.Name }} + server 127.0.0.1 down; + {{ end }} + {{ end }} + {{ end }} + {{ end }} +} +{{ end }} + + {{ if $is_https }} {{ if eq $https_method "redirect" }} server { server_name {{ $host }}; - listen {{ $external_http_port }} {{ $default_server }}; + listen {{ $defaut_external_http_port }} {{ $default_server }}; {{ if $enable_ipv6 }} - listen [::]:{{ $external_http_port }} {{ $default_server }}; + listen [::]:{{ $defaut_external_http_port }} {{ $default_server }}; {{ end }} {{ $access_log }} - + # Do not HTTPS redirect Let'sEncrypt ACME challenge location /.well-known/acme-challenge/ { auth_basic off; @@ -260,7 +277,7 @@ server { try_files $uri =404; break; } - + location / { return 301 https://$host$request_uri; } @@ -269,9 +286,10 @@ server { server { server_name {{ $host }}; - listen {{ $external_https_port }} ssl http2 {{ $default_server }}; +{{ range $port, $containers := $hostPortContainersMap }} + listen {{ $port }} ssl http2 {{ $default_server }}; {{ if $enable_ipv6 }} - listen [::]:{{ $external_https_port }} ssl http2 {{ $default_server }}; + listen [::]:{{ $port }} ssl http2 {{ $default_server }}; {{ end }} {{ $access_log }} @@ -312,15 +330,15 @@ server { location / { {{ if eq $proto "uwsgi" }} include uwsgi_params; - uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; + uwsgi_pass {{ trim $proto }}://{{ trim $proxypass_variable }}; {{ else if eq $proto "fastcgi" }} root {{ trim $vhost_root }}; include fastcgi_params; - fastcgi_pass {{ trim $upstream_name }}; + fastcgi_pass {{ trim $proxypass_variable }}; {{ else if eq $proto "grpc" }} - grpc_pass {{ trim $proto }}://{{ trim $upstream_name }}; + grpc_pass {{ trim $proto }}://{{ trim $proxypass_variable }}; {{ else }} - proxy_pass {{ trim $proto }}://{{ trim $upstream_name }}; + proxy_pass {{ trim $proto }}://{{ trim $proxypass_variable }}; {{ end }} {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} @@ -341,9 +359,10 @@ server { server { server_name {{ $host }}; - listen {{ $external_http_port }} {{ $default_server }}; +{{ range $port, $containers := $hostPortContainersMap }} + listen {{ $port }} {{ $default_server }}; {{ if $enable_ipv6 }} - listen [::]:80 {{ $default_server }}; + listen [::]:{{ $port }} {{ $default_server }}; {{ end }} {{ $access_log }} @@ -386,9 +405,9 @@ server { {{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} server { server_name {{ $host }}; - listen {{ $external_https_port }} ssl http2 {{ $default_server }}; + listen {{ $default_external_https_port }} ssl http2 {{ $default_server }}; {{ if $enable_ipv6 }} - listen [::]:{{ $external_https_port }} ssl http2 {{ $default_server }}; + listen [::]:{{ $default_external_https_port }} ssl http2 {{ $default_server }}; {{ end }} {{ $access_log }} return 500; @@ -399,4 +418,4 @@ server { {{ end }} {{ end }} -{{ end }} +{{ end }} \ No newline at end of file