diff --git a/debian/control b/debian/control index 4d5e30a93..7060a84c1 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Version: 0.38.34 Section: web Priority: optional Architecture: amd64 -Depends: apache2-utils, locales, git, cpio, cron | cron-daemon, curl, logrotate, man-db, netcat, sshcommand, docker-engine-cs (>= 19.03.0) | docker-engine (>= 19.03.0) | docker-io (>= 19.03.0) | docker.io (>= 19.03.0) | docker-ce (>= 19.03.0) | docker-ee (>= 19.03.0) | moby-engine, docker-compose-plugin | moby-compose | docker-compose, docker-buildx-plugin | moby-buildx | docker-buildx, coredns-docker, docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, parallel, procfile-util, rsync, dos2unix, unzip, util-linux +Depends: apache2-utils, locales, git, cpio, cron | cron-daemon, curl, logrotate, man-db, netcat, sshcommand, docker-engine-cs (>= 19.03.0) | docker-engine (>= 19.03.0) | docker-io (>= 19.03.0) | docker.io (>= 19.03.0) | docker-ce (>= 19.03.0) | docker-ee (>= 19.03.0) | moby-engine, docker-compose-plugin | moby-compose | docker-compose, docker-buildx-plugin | moby-buildx | docker-buildx, coredns-docker (>= 0.6.0), docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, parallel, procfile-util, rsync, dos2unix, unzip, util-linux Recommends: herokuish, bash-completion, dokku-update, dokku-event-listener Pre-Depends: gliderlabs-sigil, jq, nginx (>= 1.8.0) | openresty, bind9-dnsutils, debconf, plugn, sudo, python3, rsyslog Maintainer: Jose Diaz-Gonzalez diff --git a/docs/appendices/0.39.0-migration-guide.md b/docs/appendices/0.39.0-migration-guide.md index 616ddf5f1..c685b6ac0 100644 --- a/docs/appendices/0.39.0-migration-guide.md +++ b/docs/appendices/0.39.0-migration-guide.md @@ -8,22 +8,22 @@ - The internal `buildpack-stack-name` plugin trigger has been removed. Each builder now reads its own `stack` property directly during the build. - Changing a builder's `stack` now clears that builder's build cache so the next deploy starts fresh. For herokuish this removes the `cache-` Docker volume; for pack the next `pack build` runs with `--clear-cache`. - The `dokku` Debian package now declares `coredns-docker (>= 0.6.0)` as a hard `Depends:`. `apt install dokku` and `apt upgrade dokku` pull it in automatically. The deb ships a default `/etc/coredns/Corefile` that binds `127.0.0.1:1053`, serves the `docker.` zone, and synthesizes `..docker` and `.docker` `A` records from the `com.dokku.app-name` and `com.dokku.process-type` labels Dokku already stamps on every deployed container. The Docker image of Dokku ships coredns-docker preinstalled and runs it under runit on the same port. -- The bundled `nginx.conf.sigil` now uses an nginx `resolver` directive and a variable-based `proxy_pass` against `..docker` instead of pinning the upstream to a literal IP. This is what addresses the long-standing post-reboot and post-`apt-upgrade-docker` stale-vhost bug ([#8313](https://github.com/dokku/dokku/issues/8313)) - the rendered upstream re-resolves at request time via coredns-docker, so a host reboot or single-container restart no longer leaves nginx routing to a stale IP. See the [nginx documentation](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for details. -- Three new global nginx properties control the resolver path: - - - `dns-resolver` (default: `127.0.0.1:1053`) - the address nginx talks to. - - `dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The short default keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable. - - `dns-zone` (default: `docker`) - the suffix appended to `.` when constructing the upstream hostname. - - All three can be set via `dokku nginx:set --global `. -- The literal string `off` is the sentinel for disabling the resolver path. **Setting `dns-resolver` to the empty string does not disable it** - that deletes the property file and the new default (`127.0.0.1:1053`) re-engages. To revert to the legacy IP-based upstream block on hosts where coredns-docker cannot run (source/git installs, locally held coredns-docker package, postinst failures): +- The bundled `nginx.conf.sigil` can now use an nginx `resolver` directive and a variable-based `proxy_pass` against `..docker` instead of pinning the upstream to a literal IP. This addresses the long-standing post-reboot and post-`apt-upgrade-docker` stale-vhost bug ([#8313](https://github.com/dokku/dokku/issues/8313)) - the rendered upstream re-resolves at request time via coredns-docker, so a host reboot or single-container restart no longer leaves nginx routing to a stale IP. This behavior is opt-in, and upstreams remain IP-based unless `dns-resolver` is set: ```shell - dokku nginx:set --global dns-resolver off + dokku nginx:set --global dns-resolver 127.0.0.1:1053 dokku proxy:build-config --all ``` -- Vendored `nginx.conf.sigil` templates are unaffected by the change. The existing `DOKKU_APP__LISTENERS` variable continues to be populated with the IP-based listener list exactly as before, so templates that only reference it continue to render IP-based upstreams regardless of the new property defaults. To opt a vendored template into the resolver path, switch its `proxy_pass` line to use the new `DOKKU_APP__LISTENER_HOST`, `NGINX_DNS_RESOLVER`, `NGINX_DNS_RESOLVER_TIMEOUT`, and `NGINX_DNS_ZONE` sigil variables - see the [nginx.conf.sigil reference](/docs/appendices/file-formats/nginx-conf-sigil.md#upstream-rendering-ip-based-vs-dns-based) for the exact snippet. + See the [nginx documentation](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for details. +- Three new nginx properties control the resolver path: + + - `dns-resolver` (default: none) - the address nginx talks to. Resolver-based upstreams are only rendered when this is set. + - `dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The short default keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable. + - `dns-zone` (default: `docker`) - the suffix appended to `.` when constructing the upstream hostname. + + All three can be set per app or globally via `dokku nginx:set`. +- Vendored `nginx.conf.sigil` templates are unaffected by the change. The existing `DOKKU_APP__LISTENERS` variable continues to be populated with the IP-based listener list exactly as before, so templates that only reference it continue to render IP-based upstreams regardless of the new properties. To opt a vendored template into the resolver path, switch its `proxy_pass` line to use the new `DOKKU_APP__LISTENER_HOST`, `NGINX_DNS_RESOLVER`, `NGINX_DNS_RESOLVER_TIMEOUT`, and `NGINX_DNS_ZONE` sigil variables - see the [nginx.conf.sigil reference](/docs/appendices/file-formats/nginx-conf-sigil.md#upstream-rendering-ip-based-vs-dns-based) for the exact snippet. ### Stack property migrated to builder plugins diff --git a/docs/appendices/file-formats/nginx-conf-sigil.md b/docs/appendices/file-formats/nginx-conf-sigil.md index 2d7ef8b6e..c0324cacf 100644 --- a/docs/appendices/file-formats/nginx-conf-sigil.md +++ b/docs/appendices/file-formats/nginx-conf-sigil.md @@ -101,7 +101,7 @@ The new variables (`DOKKU_APP__LISTENER_HOST` + `NGINX_DNS_RESOLVER` + `NG The bundled template emits this snippet: ```nginx -{{ if ne $.NGINX_DNS_RESOLVER "off" }} +{{ if $.NGINX_DNS_RESOLVER }} resolver {{ $.NGINX_DNS_RESOLVER }} valid=10s ipv6=off; resolver_timeout {{ $.NGINX_DNS_RESOLVER_TIMEOUT }}; set $dokku_upstream "{{ $.DOKKU_APP_WEB_LISTENER_HOST }}:{{ $upstream_port }}"; @@ -113,8 +113,8 @@ proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; The `$dokku_upstream` variable name is intentionally namespaced (rather than `$upstream`) so vendored templates that already define `$upstream` will not collide if they adopt the resolver branch. -`NGINX_DNS_RESOLVER` defaults to `127.0.0.1:1053`, `NGINX_DNS_RESOLVER_TIMEOUT` defaults to `5s`, and `NGINX_DNS_ZONE` defaults to `docker`; all three are configurable via `dokku nginx:set --global dns-resolver ...`, `dokku nginx:set --global dns-resolver-timeout ...`, and `dokku nginx:set --global dns-zone ...`. The literal string `off` is the sentinel for disabling the resolver branch in the bundled template - empty strings are treated as "use the default" because the property is stored on disk and an empty value deletes the property file. The short default `dns-resolver-timeout` keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable; nginx returns 502 after the configured interval instead. +`NGINX_DNS_RESOLVER` is empty by default, `NGINX_DNS_RESOLVER_TIMEOUT` defaults to `5s`, and `NGINX_DNS_ZONE` defaults to `docker`; all three are configurable via `dokku nginx:set --global dns-resolver ...`, `dokku nginx:set --global dns-resolver-timeout ...`, and `dokku nginx:set --global dns-zone ...`. The bundled template only renders the resolver branch when `NGINX_DNS_RESOLVER` is set, for example to `127.0.0.1:1053`. Unsetting `dns-resolver` switches the bundled template back to the IP-based upstream. The short default `dns-resolver-timeout` keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable; nginx returns 502 after the configured interval instead. ### Choosing between the two -The DNS-based path is the default for the bundled template. Vendored templates that have not been updated continue to use `DOKKU_APP__LISTENERS` and are unaffected by the global default. The DNS-based variables are populated regardless of whether `dns-resolver` is `off` or not, so a vendored template can adopt the resolver path on its own schedule by switching its rendering snippet, even if other apps on the host are still using IP-based upstreams. +The IP-based path is the default for the bundled template, and the DNS-based path is used once `dns-resolver` is set. Vendored templates that have not been updated continue to use `DOKKU_APP__LISTENERS` and are unaffected by the `dns-resolver` setting. The `DOKKU_APP__LISTENER_HOST` and `NGINX_DNS_ZONE` variables are populated regardless of whether `dns-resolver` is set, so a vendored template can adopt the resolver path on its own schedule by switching its rendering snippet, even if other apps on the host are still using IP-based upstreams. diff --git a/docs/getting-started/install/docker.md b/docs/getting-started/install/docker.md index 15d7efbe7..e79a841d7 100644 --- a/docs/getting-started/install/docker.md +++ b/docs/getting-started/install/docker.md @@ -88,7 +88,7 @@ Dokku is run in the following configuration: - Image build cache is set to the data dir + `/home/dokku`. - The docker socket is mounted into container. - `DOKKU_HOST_ROOT` and `DOKKU_LIB_HOST_ROOT` tell Dokku how the host Docker daemon sees those same trees when creating bind mounts (app repos and plugin data such as named storage). -- `coredns-docker` (a CoreDNS plugin that serves DNS records for sibling Docker containers) is installed transitively via the dokku deb's `Depends:`, runs under runit on `127.0.0.1:1053` inside the container, and is what the `nginx-vhosts` `dns-resolver` default points at. This is what makes the `..docker` upstream resolution work out of the box - see the [nginx proxy doc](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for the operator-facing behavior. +- `coredns-docker` (a CoreDNS plugin that serves DNS records for sibling Docker containers) is installed transitively via the dokku deb's `Depends:`, runs under runit on `127.0.0.1:1053` inside the container, and is what the `nginx-vhosts` `dns-resolver` property can point at to enable `..docker` upstream resolution - see the [nginx proxy doc](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for the operator-facing behavior. Application repositories, plugin config, as well as plugin data are persisted to disk within the specified host directory for `/var/lib/dokku`. `DOKKU_LIB_HOST_ROOT` must point at that host path's `var/lib/dokku` subtree so Docker `-v` mounts resolve under the data volume rather than under the host's own `/var/lib/dokku`. diff --git a/docs/networking/proxies/nginx.md b/docs/networking/proxies/nginx.md index ede8157ae..1e07b6758 100644 --- a/docs/networking/proxies/nginx.md +++ b/docs/networking/proxies/nginx.md @@ -33,27 +33,34 @@ Nginx will proxy the requests in a [round-robin balancing fashion](http://nginx. > [!IMPORTANT] > New as of 0.39.0 -Dokku resolves nginx upstreams through [coredns-docker](https://github.com/dokku/coredns-docker), a CoreDNS plugin that watches the Docker daemon and serves fresh `A` records keyed off container labels. The nginx `resolver` directive points at coredns-docker's local socket (`127.0.0.1:1053`) and the rendered upstream is a variable-based `proxy_pass`, which forces nginx to re-resolve at request time honoring DNS TTL. This is how nginx avoids serving the wrong vhost after a host reboot, after `apt upgrade docker`, or after a single container restarts mid-runtime: the on-disk nginx config no longer pins a stale IP. +Dokku can resolve nginx upstreams through [coredns-docker](https://github.com/dokku/coredns-docker), a CoreDNS plugin that watches the Docker daemon and serves fresh `A` records keyed off container labels. When enabled, the nginx `resolver` directive points at coredns-docker's local socket (`127.0.0.1:1053`) and the rendered upstream is a variable-based `proxy_pass`, which forces nginx to re-resolve at request time honoring DNS TTL. This is how nginx avoids serving the wrong vhost after a host reboot, after `apt upgrade docker`, or after a single container restarts mid-runtime: the on-disk nginx config no longer pins a stale IP. This behavior is opt-in, and nginx uses IP-based `upstream` blocks by default. `coredns-docker` (>= `0.6.0`) is installed automatically as a transitive `Depends:` of the `dokku` Debian package, so `apt install dokku` and `apt upgrade dokku` pull it in without operator action. The shipped `/etc/coredns/Corefile` already binds `127.0.0.1:1053`, serves the `docker.` zone, and synthesizes `..docker` and `.docker` `A` records from Dokku's existing `com.dokku.app-name` and `com.dokku.process-type` container labels. Multiple containers for the same process type collapse onto one multi-A record set, so scaled apps load-balance across instances via DNS without any per-container configuration. The bundled Docker image of Dokku ships coredns-docker preinstalled and runs it under runit on the same port. No `systemd-resolved` or `/etc/resolv.conf` change is required - nginx talks to `coredns-docker` directly via the `resolver` directive in each app's `nginx.conf`. -The behavior is controlled by three global nginx properties: - -- `dns-resolver` (default: `127.0.0.1:1053`) - the resolver address nginx talks to. -- `dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The default is short so requests fail fast when coredns-docker is unreachable rather than hanging on the nginx built-in default (30s). -- `dns-zone` (default: `docker`) - the suffix appended to `.` when constructing the upstream hostname. - -To revert to the legacy IP-based upstream block (for instance on a host where `coredns-docker` cannot be installed): +To enable resolver-based upstreams: ```shell -dokku nginx:set --global dns-resolver off +dokku nginx:set --global dns-resolver 127.0.0.1:1053 dokku proxy:build-config --all ``` +To revert to the IP-based upstream block, unset the property and rebuild the config: + +```shell +dokku nginx:set --global dns-resolver +dokku proxy:build-config --all +``` + +The behavior is controlled by three nginx properties, each settable per app or globally: + +- `dns-resolver` (default: none) - the resolver address nginx talks to. Resolver-based upstreams are only rendered when this is set. +- `dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The default is short so requests fail fast when coredns-docker is unreachable rather than hanging on the nginx built-in default (30s). +- `dns-zone` (default: `docker`) - the suffix appended to `.` when constructing the upstream hostname. + > [!NOTE] -> The sentinel for disabling the feature is the literal string `off`. Setting `dns-resolver` to the empty string (`""`) does **not** disable it - that deletes the property and the default (`127.0.0.1:1053`) re-engages. Use `off` to disable, the default to re-enable. +> When `dns-resolver` is set, no `upstream` block is rendered for the `web` process, so `proxy-keepalive` has no effect on it. Upstreams for [proxy routes](/docs/networking/proxy-management.md#path-based-routing-to-non-web-processes) to other process types remain IP-based. See [the `nginx.conf.sigil` reference](/docs/appendices/file-formats/nginx-conf-sigil.md) for the sigil variables that drive the resolver-based render, and how vendored templates can adopt or skip it. @@ -672,6 +679,9 @@ All nginx-vhosts properties are settable at both the app and global scope. | `client-header-timeout` | app + global | `60s` | `--nginx-client-header-timeout`, `--nginx-global-client-header-timeout`, `--nginx-computed-client-header-timeout` | Time allowed to read the request header from the client | | `client-max-body-size` | app + global | `1m` | `--nginx-client-max-body-size`, `--nginx-global-client-max-body-size`, `--nginx-computed-client-max-body-size` | Maximum allowed request body size | | `disable-custom-config` | app + global | `false` | `--nginx-disable-custom-config`, `--nginx-global-disable-custom-config`, `--nginx-computed-disable-custom-config` | When `true`, ignores app-supplied `nginx.conf.d/*.conf` snippets | +| `dns-resolver` | app + global | none | `--nginx-dns-resolver`, `--nginx-global-dns-resolver`, `--nginx-computed-dns-resolver` | Resolver address used for DNS-based upstreams. When unset, upstreams are IP-based | +| `dns-resolver-timeout` | app + global | `5s` | `--nginx-dns-resolver-timeout`, `--nginx-global-dns-resolver-timeout`, `--nginx-computed-dns-resolver-timeout` | Time nginx waits for a DNS response from the resolver | +| `dns-zone` | app + global | `docker` | `--nginx-dns-zone`, `--nginx-global-dns-zone`, `--nginx-computed-dns-zone` | Zone appended to `.` for DNS-based upstream hostnames | | `error-log-path` | app + global | _`{nginx-log-root}/{app}-error.log`_ | `--nginx-error-log-path`, `--nginx-global-error-log-path`, `--nginx-computed-error-log-path` | Path inside the nginx container where error logs are written | | `hsts` | app + global | `true` | `--nginx-hsts`, `--nginx-global-hsts`, `--nginx-computed-hsts` | When `true`, emits a `Strict-Transport-Security` header on HTTPS responses | | `hsts-include-subdomains` | app + global | `true` | `--nginx-hsts-include-subdomains`, `--nginx-global-hsts-include-subdomains`, `--nginx-computed-hsts-include-subdomains` | Adds the `includeSubDomains` directive to the HSTS header | diff --git a/plugins/nginx-vhosts/nginx_vhosts.go b/plugins/nginx-vhosts/nginx_vhosts.go index 7d562e350..7a78c84f1 100644 --- a/plugins/nginx-vhosts/nginx_vhosts.go +++ b/plugins/nginx-vhosts/nginx_vhosts.go @@ -164,7 +164,7 @@ func ComputedDnsResolver(appName string) string { } func GlobalDnsResolver() string { - return common.PropertyGetDefault("nginx", "--global", "dns-resolver", "127.0.0.1:1053") + return common.PropertyGet("nginx", "--global", "dns-resolver") } func AppDnsResolverTimeout(appName string) string { diff --git a/plugins/nginx-vhosts/template_test.go b/plugins/nginx-vhosts/template_test.go index 0d95c27e6..7d9927743 100644 --- a/plugins/nginx-vhosts/template_test.go +++ b/plugins/nginx-vhosts/template_test.go @@ -38,7 +38,7 @@ func defaultVars() map[string]interface{} { "PROXY_KEEPALIVE": "", "NGINX_BIND_ADDRESS_IP4": "", "NGINX_BIND_ADDRESS_IP6": "::", - "NGINX_DNS_RESOLVER": "127.0.0.1:1053", + "NGINX_DNS_RESOLVER": "", "NGINX_DNS_RESOLVER_TIMEOUT": "5s", "NGINX_DNS_ZONE": "docker", "NGINX_ACCESS_LOG_PATH": "/var/log/nginx/app-access.log", @@ -99,11 +99,10 @@ func mustNotContain(t *testing.T, out, needle string) { func TestTemplate_HTTPOnlyBasicProxy(t *testing.T) { out := renderTemplate(t, defaultVars()) mustContain(t, out, "listen [::]:80;") - mustContain(t, out, "resolver 127.0.0.1:1053 valid=10s ipv6=off;") - mustContain(t, out, "resolver_timeout 5s;") - mustContain(t, out, `set $dokku_upstream "app.web.docker:5000";`) - mustContain(t, out, "proxy_pass http://$dokku_upstream;") - mustNotContain(t, out, "upstream app-5000 {") + mustContain(t, out, "proxy_pass http://app-5000;") + mustContain(t, out, "upstream app-5000 {") + mustNotContain(t, out, "resolver ") + mustNotContain(t, out, "$dokku_upstream") mustContain(t, out, "error_page 500 501 502 503") mustContain(t, out, "server_name app.example.com;") mustNotContain(t, out, "ssl_certificate") @@ -113,21 +112,23 @@ func TestTemplate_HTTPOnlyBasicProxy(t *testing.T) { func TestTemplate_ResolverTimeoutOverride(t *testing.T) { v := defaultVars() + v["NGINX_DNS_RESOLVER"] = "127.0.0.1:1053" v["NGINX_DNS_RESOLVER_TIMEOUT"] = "2s" out := renderTemplate(t, v) mustContain(t, out, "resolver_timeout 2s;") mustNotContain(t, out, "resolver_timeout 5s;") } -func TestTemplate_HTTPOnlyBasicProxyWithResolverOff(t *testing.T) { +func TestTemplate_HTTPOnlyBasicProxyWithResolver(t *testing.T) { v := defaultVars() - v["NGINX_DNS_RESOLVER"] = "off" + v["NGINX_DNS_RESOLVER"] = "127.0.0.1:1053" out := renderTemplate(t, v) mustContain(t, out, "listen [::]:80;") - mustContain(t, out, "proxy_pass http://app-5000;") - mustContain(t, out, "upstream app-5000 {") - mustNotContain(t, out, "resolver 127.0.0.1:1053") - mustNotContain(t, out, "$dokku_upstream") + mustContain(t, out, "resolver 127.0.0.1:1053 valid=10s ipv6=off;") + mustContain(t, out, "resolver_timeout 5s;") + mustContain(t, out, `set $dokku_upstream "app.web.docker:5000";`) + mustContain(t, out, "proxy_pass http://$dokku_upstream;") + mustNotContain(t, out, "upstream app-5000 {") } func TestTemplate_HTTPSEmitsPushPreloadWhenSupported(t *testing.T) { @@ -155,7 +156,6 @@ func TestTemplate_HTTPRedirectsToHTTPSWhenSSLInUse(t *testing.T) { v["PROXY_PORT_MAP"] = "http:80:5000 https:443:5000" v["SSL_INUSE"] = "true" v["SSL_SERVER_NAME"] = "app.example.com" - v["NGINX_DNS_RESOLVER"] = "off" out := renderTemplate(t, v) port80, _, ok := strings.Cut(out, "listen [::]:443") @@ -206,7 +206,6 @@ func TestTemplate_GRPCNoSSL(t *testing.T) { v := defaultVars() v["PROXY_PORT_MAP"] = "grpc:50051:50051" v["PROXY_UPSTREAM_PORTS"] = "50051" - v["NGINX_DNS_RESOLVER"] = "off" out := renderTemplate(t, v) mustContain(t, out, "grpc_pass grpc://app-50051;") mustContain(t, out, "http2") @@ -215,6 +214,7 @@ func TestTemplate_GRPCNoSSL(t *testing.T) { func TestTemplate_GRPCNoSSLWithResolver(t *testing.T) { v := defaultVars() + v["NGINX_DNS_RESOLVER"] = "127.0.0.1:1053" v["PROXY_PORT_MAP"] = "grpc:50051:50051" v["PROXY_UPSTREAM_PORTS"] = "50051" out := renderTemplate(t, v) @@ -229,7 +229,6 @@ func TestTemplate_GRPCS(t *testing.T) { v["PROXY_UPSTREAM_PORTS"] = "50051" v["SSL_INUSE"] = "true" v["SSL_SERVER_NAME"] = "app.example.com" - v["NGINX_DNS_RESOLVER"] = "off" out := renderTemplate(t, v) mustContain(t, out, "ssl_certificate /home/dokku/app/tls/server.crt;") mustContain(t, out, "grpc_pass grpc://app-50051;") @@ -262,7 +261,6 @@ func TestTemplate_UpstreamBlock(t *testing.T) { v["PROXY_PORT_MAP"] = "http:80:5000 http:8080:5001" v["PROXY_UPSTREAM_PORTS"] = "5000 5001" v["DOKKU_APP_WEB_LISTENERS"] = "10.0.0.1:5000 10.0.0.2:5000" - v["NGINX_DNS_RESOLVER"] = "off" out := renderTemplate(t, v) mustContain(t, out, "upstream app-5000 {") mustContain(t, out, "upstream app-5001 {") @@ -274,6 +272,7 @@ func TestTemplate_UpstreamBlock(t *testing.T) { func TestTemplate_UpstreamBlockOmittedInResolverMode(t *testing.T) { v := defaultVars() + v["NGINX_DNS_RESOLVER"] = "127.0.0.1:1053" v["PROXY_PORT_MAP"] = "http:80:5000 http:8080:5001" v["PROXY_UPSTREAM_PORTS"] = "5000 5001" v["DOKKU_APP_WEB_LISTENERS"] = "10.0.0.1:5000 10.0.0.2:5000" @@ -286,12 +285,10 @@ func TestTemplate_UpstreamBlockOmittedInResolverMode(t *testing.T) { func TestTemplate_UpstreamWithKeepalive(t *testing.T) { v := defaultVars() v["PROXY_KEEPALIVE"] = "16" - v["NGINX_DNS_RESOLVER"] = "off" out := renderTemplate(t, v) mustContain(t, out, "keepalive 16;") v2 := defaultVars() - v2["NGINX_DNS_RESOLVER"] = "off" out2 := renderTemplate(t, v2) mustNotContain(t, out2, "keepalive 16;") } @@ -335,6 +332,18 @@ func TestTemplate_RouteLocationAndUpstream(t *testing.T) { mustContain(t, out, "server 10.0.0.4:5001;") } +func TestTemplate_RouteUpstreamRenderedInResolverMode(t *testing.T) { + v := defaultVars() + v["NGINX_DNS_RESOLVER"] = "127.0.0.1:1053" + v["PROXY_ROUTES"] = "api|5001|/api/v0|0" + v["PROXY_ROUTE_LISTENERS"] = "api|5001|10.0.0.3:5001" + out := renderTemplate(t, v) + mustContain(t, out, "proxy_pass http://$dokku_upstream;") + mustContain(t, out, "proxy_pass http://app-api-5001;") + mustContain(t, out, "upstream app-api-5001 {") + mustNotContain(t, out, "upstream app-5000 {") +} + func TestTemplate_RouteStripPrefix(t *testing.T) { v := defaultVars() v["PROXY_ROUTES"] = "api|5001|/api/v0|1" diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil index 5bb22703b..ad0bb149c 100644 --- a/plugins/nginx-vhosts/templates/nginx.conf.sigil +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -84,7 +84,7 @@ server { gzip_vary on; gzip_comp_level 6; -{{ if ne $.NGINX_DNS_RESOLVER "off" }} +{{ if $.NGINX_DNS_RESOLVER }} resolver {{ $.NGINX_DNS_RESOLVER }} valid=10s ipv6=off; resolver_timeout {{ $.NGINX_DNS_RESOLVER_TIMEOUT }}; set $dokku_upstream "{{ $.DOKKU_APP_WEB_LISTENER_HOST }}:{{ $upstream_port }}"; @@ -173,7 +173,7 @@ server { {{ end }} location / { -{{ if ne $.NGINX_DNS_RESOLVER "off" }} +{{ if $.NGINX_DNS_RESOLVER }} resolver {{ $.NGINX_DNS_RESOLVER }} valid=10s ipv6=off; resolver_timeout {{ $.NGINX_DNS_RESOLVER_TIMEOUT }}; set $dokku_upstream "{{ $.DOKKU_APP_WEB_LISTENER_HOST }}:{{ $upstream_port }}"; @@ -190,7 +190,7 @@ server { {{ end }} {{ end }} -{{ if eq $.NGINX_DNS_RESOLVER "off" }} +{{ if not $.NGINX_DNS_RESOLVER }} {{ if $.DOKKU_APP_WEB_LISTENERS }} {{ range $upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} upstream {{ $.APP }}-{{ $upstream_port }} { diff --git a/tests/test_coredns_outage b/tests/test_coredns_outage index 4b4ebc0f7..fe9b4b125 100755 --- a/tests/test_coredns_outage +++ b/tests/test_coredns_outage @@ -12,6 +12,7 @@ cleanup() { set +e start_coredns_docker ssh "$REMOTE" apps:destroy "$APP" --force >/dev/null 2>&1 + ssh "$REMOTE" nginx:set --global dns-resolver >/dev/null 2>&1 ssh "$REMOTE" events:off >/dev/null 2>&1 exit $exit_code } @@ -65,6 +66,7 @@ wait_for_status() { trap cleanup INT TERM EXIT ssh "$REMOTE" events:on +ssh "$REMOTE" nginx:set --global dns-resolver 127.0.0.1:1053 TMP=$(mktemp -d "/tmp/$TARGET.XXXXX") rmdir "$TMP" && cp -r "$(dirname "$SELF")/apps/python" "$TMP" diff --git a/tests/unit/nginx-vhosts_17.bats b/tests/unit/nginx-vhosts_17.bats index 2e9fe0865..d34145d06 100644 --- a/tests/unit/nginx-vhosts_17.bats +++ b/tests/unit/nginx-vhosts_17.bats @@ -17,12 +17,12 @@ teardown() { global_teardown } -@test "(nginx-vhosts:set) dns-resolver and dns-zone defaults report 127.0.0.1:1053 and docker" { +@test "(nginx-vhosts:set) dns-resolver defaults to empty and dns-zone defaults to docker" { run /bin/bash -c "dokku nginx:report $TEST_APP --nginx-computed-dns-resolver" echo "output: $output" echo "status: $status" assert_success - assert_output "127.0.0.1:1053" + assert_output "" run /bin/bash -c "dokku nginx:report $TEST_APP --nginx-computed-dns-zone" echo "output: $output" @@ -55,7 +55,27 @@ teardown() { assert_output "test" } -@test "(nginx-vhosts) generated nginx.conf uses resolver and variable-based proxy_pass by default" { +@test "(nginx-vhosts) generated nginx.conf uses IP-based upstream by default" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "resolver " + assert_output_not_contains "\$dokku_upstream" + assert_output_contains "upstream ${TEST_APP}-" +} + +@test "(nginx-vhosts) generated nginx.conf uses resolver and variable-based proxy_pass when dns-resolver is set" { + run /bin/bash -c "dokku nginx:set --global dns-resolver 127.0.0.1:1053" + echo "output: $output" + echo "status: $status" + assert_success + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" @@ -72,8 +92,8 @@ teardown() { assert_output_not_contains "upstream ${TEST_APP}-" } -@test "(nginx-vhosts) generated nginx.conf falls back to IP-based upstream when dns-resolver=off" { - run /bin/bash -c "dokku nginx:set --global dns-resolver off" +@test "(nginx-vhosts) unsetting dns-resolver reverts to IP-based upstream" { + run /bin/bash -c "dokku nginx:set --global dns-resolver 127.0.0.1:1053" echo "output: $output" echo "status: $status" assert_success @@ -87,24 +107,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_not_contains "resolver 127.0.0.1:1053" - assert_output_not_contains "\$dokku_upstream" - assert_output_contains "upstream ${TEST_APP}-" -} - -@test "(nginx-vhosts) setting dns-resolver to empty re-engages the default resolver path" { - run /bin/bash -c "dokku nginx:set --global dns-resolver off" - echo "output: $output" - echo "status: $status" - assert_success - - run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku nginx:show-config $TEST_APP" - assert_output_contains "upstream ${TEST_APP}-" + assert_output_contains "resolver 127.0.0.1:1053 valid=10s ipv6=off" run /bin/bash -c "dokku nginx:set --global dns-resolver" echo "output: $output" @@ -120,11 +123,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "resolver 127.0.0.1:1053 valid=10s ipv6=off" - assert_output_not_contains "upstream ${TEST_APP}-" + assert_output_not_contains "resolver 127.0.0.1:1053" + assert_output_contains "upstream ${TEST_APP}-" } @test "(nginx-vhosts) vendored nginx.conf.sigil renders IP-based upstream regardless of dns-resolver" { + run /bin/bash -c "dokku nginx:set --global dns-resolver 127.0.0.1:1053" + echo "output: $output" + echo "status: $status" + assert_success + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template echo "output: $output" echo "status: $status" @@ -140,6 +148,11 @@ teardown() { } @test "(nginx-vhosts) nginx returns 502 when coredns-docker is stopped" { + run /bin/bash -c "dokku nginx:set --global dns-resolver 127.0.0.1:1053" + echo "output: $output" + echo "status: $status" + assert_success + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status"