1
0
mirror of https://github.com/thib8956/nginx-proxy synced 2025-02-24 01:38:15 +00:00

Merge pull request #2499 from nginx-proxy/ipv6

feat: basic implementation of IPv6 for IPv6 docker networks
This commit is contained in:
Nicolas Duchon 2024-12-08 11:58:12 +01:00 committed by GitHub
commit b6c8851794
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 182 additions and 14 deletions

View File

@ -36,6 +36,15 @@ jobs:
pip install -r python-requirements.txt pip install -r python-requirements.txt
working-directory: test/requirements working-directory: test/requirements
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Pull nginx:alpine image
run: docker pull nginx:alpine
- name: Build Docker web server image - name: Build Docker web server image
run: make build-webserver run: make build-webserver

View File

@ -618,7 +618,19 @@ If the default certificate is also missing, nginx-proxy will:
## IPv6 Support ## IPv6 Support
You can activate the IPv6 support for the nginx-proxy container by passing the value `true` to the `ENABLE_IPV6` environment variable: ### IPv6 Docker Networks
nginx-proxy support both IPv4 and IPv6 on Docker networks.
By default nginx-proxy will prefer IPv4: if a container can be reached over both IPv4 and IPv6, only its IPv4 will be used.
This can be changed globally by setting the environment variable `PREFER_IPV6_NETWORK` to `true` on the proxy container: with this setting the proxy will only use IPv6 for containers that can be reached over both IPv4 and IPv6.
IPv4 and IPv6 are never both used at the same time on containers that use both IP stacks to avoid artificially inflating the effective round robin weight of those containers.
### Listening on IPv6
By default the nginx-proxy container will only listen on IPv4. To enable listening on IPv6 too, set the `ENABLE_IPV6` environment variable to `true`:
```console ```console
docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy

View File

@ -23,6 +23,7 @@
{{- $_ := set $config "trust_downstream_proxy" ($globals.Env.TRUST_DOWNSTREAM_PROXY | default "true" | parseBool) }} {{- $_ := set $config "trust_downstream_proxy" ($globals.Env.TRUST_DOWNSTREAM_PROXY | default "true" | parseBool) }}
{{- $_ := set $config "enable_access_log" ($globals.Env.DISABLE_ACCESS_LOGS | default "false" | parseBool | not) }} {{- $_ := set $config "enable_access_log" ($globals.Env.DISABLE_ACCESS_LOGS | default "false" | parseBool | not) }}
{{- $_ := set $config "enable_ipv6" ($globals.Env.ENABLE_IPV6 | default "false" | parseBool) }} {{- $_ := set $config "enable_ipv6" ($globals.Env.ENABLE_IPV6 | default "false" | parseBool) }}
{{- $_ := set $config "prefer_ipv6_network" ($globals.Env.PREFER_IPV6_NETWORK | default "false" | parseBool) }}
{{- $_ := set $config "ssl_policy" ($globals.Env.SSL_POLICY | default "Mozilla-Intermediate") }} {{- $_ := set $config "ssl_policy" ($globals.Env.SSL_POLICY | default "Mozilla-Intermediate") }}
{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }} {{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }}
{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }} {{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }}
@ -76,7 +77,8 @@
* The return value will be added to the dot dict with key "ip". * The return value will be added to the dot dict with key "ip".
*/}} */}}
{{- define "container_ip" }} {{- define "container_ip" }}
{{- $ip := "" }} {{- $ipv4 := "" }}
{{- $ipv6 := "" }}
# networks: # networks:
{{- range sortObjectsByKeysAsc $.container.Networks "Name" }} {{- range sortObjectsByKeysAsc $.container.Networks "Name" }}
{{- /* {{- /*
@ -91,17 +93,17 @@
{{- /* Handle containers in host nework mode */}} {{- /* Handle containers in host nework mode */}}
{{- if (index $.globals.networks "host") }} {{- if (index $.globals.networks "host") }}
# both container and proxy are in host network mode, using localhost IP # both container and proxy are in host network mode, using localhost IP
{{- $ip = "127.0.0.1" }} {{- $ipv4 = "127.0.0.1" }}
{{- continue }} {{- continue }}
{{- end }} {{- end }}
{{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }} {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }}
{{- if and . .Gateway (not .Internal) }} {{- if and . .Gateway (not .Internal) }}
# container is in host network mode, using {{ .Name }} gateway IP # container is in host network mode, using {{ .Name }} gateway IP
{{- $ip = .Gateway }} {{- $ipv4 = .Gateway }}
{{- break }} {{- break }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $ip }} {{- if $ipv4 }}
{{- continue }} {{- continue }}
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -111,26 +113,41 @@
{{- end }} {{- end }}
{{- /* {{- /*
* Do not emit multiple `server` directives for this container if it * Do not emit multiple `server` directives for this container if it
* is reachable over multiple networks. This avoids accidentally * is reachable over multiple networks or multiple IP stacks. This avoids
* inflating the effective round-robin weight of a server due to the * accidentally inflating the effective round-robin weight of a server due
* redundant upstream addresses that nginx sees as belonging to * to the redundant upstream addresses that nginx sees as belonging to
* distinct servers. * distinct servers.
*/}} */}}
{{- if $ip }} {{- if or $ipv4 $ipv6 }}
# {{ .Name }} (ignored; reachable but redundant) # {{ .Name }} (ignored; reachable but redundant)
{{- continue }} {{- continue }}
{{- end }} {{- end }}
# {{ .Name }} (reachable) # {{ .Name }} (reachable)
{{- if and . .IP }} {{- if and . .IP }}
{{- $ip = .IP }} {{- $ipv4 = .IP }}
{{- else }} {{- end }}
# /!\ No IP for this network! {{- if and . .GlobalIPv6Address }}
{{- $ipv6 = .GlobalIPv6Address }}
{{- end }}
{{- if and (empty $ipv4) (empty $ipv6) }}
# /!\ No IPv4 or IPv6 for this network!
{{- end }} {{- end }}
{{- else }} {{- else }}
# (none) # (none)
{{- end }} {{- end }}
# IP address: {{ if $ip }}{{ $ip }}{{ else }}(none usable){{ end }} {{ if and $ipv6 $.globals.config.prefer_ipv6_network }}
{{- $_ := set $ "ip" $ip }} # IPv4 address: {{ if $ipv4 }}{{ $ipv4 }} (ignored; reachable but IPv6 prefered){{ else }}(none usable){{ end }}
# IPv6 address: {{ $ipv6 }}
{{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
{{- else }}
# IPv4 address: {{ if $ipv4 }}{{ $ipv4 }}{{ else }}(none usable){{ end }}
# IPv6 address: {{ if $ipv6 }}{{ $ipv6 }}{{ if $ipv4 }} (ignored; reachable but IPv4 prefered){{ end }}{{ else }}(none usable){{ end }}
{{- if $ipv4 }}
{{- $_ := set $ "ip" $ipv4 }}
{{- else if $ipv6}}
{{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
{{- end }}
{{- end }}
{{- end }} {{- end }}
{{- /* {{- /*

View File

@ -0,0 +1,19 @@
import pytest
def test_forwards_to_ipv4_only_network(docker_compose, nginxproxy):
r = nginxproxy.get("http://ipv4only.nginx-proxy.tld/port")
assert r.status_code == 200
assert r.text == "answer from port 80\n"
def test_forwards_to_dualstack_network(docker_compose, nginxproxy):
r = nginxproxy.get("http://dualstack.nginx-proxy.tld")
assert r.status_code == 200
assert "Welcome to nginx!" in r.text
def test_dualstack_network_prefer_ipv4_config(docker_compose, nginxproxy):
conf = nginxproxy.get_conf().decode('ASCII')
assert "IPv6 address: fd00:cafe:face:feed::2 (ignored; reachable but IPv4 prefered)" in conf
assert "server 172.16.20.2:80;" in conf

View File

@ -0,0 +1,45 @@
version: "2"
networks:
ipv4net:
ipam:
config:
- subnet: 172.16.10.0/24
dualstacknet:
enable_ipv6: true
ipam:
config:
- subnet: 172.16.20.0/24
- subnet: fd00:cafe:face:feed::/64
services:
ipv4only:
image: web
expose:
- "80"
environment:
WEB_PORTS: 80
VIRTUAL_HOST: ipv4only.nginx-proxy.tld
networks:
ipv4net:
ipv4_address: 172.16.10.2
dualstack:
image: nginx:alpine
environment:
VIRTUAL_HOST: dualstack.nginx-proxy.tld
networks:
dualstacknet:
ipv4_address: 172.16.20.2
ipv6_address: fd00:cafe:face:feed::2
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
ipv4net:
ipv4_address: 172.16.10.3
dualstacknet:
ipv4_address: 172.16.20.3
ipv6_address: fd00:cafe:face:feed::3

View File

@ -0,0 +1,19 @@
import pytest
def test_forwards_to_ipv4_only_network(docker_compose, nginxproxy):
r = nginxproxy.get("http://ipv4only.nginx-proxy.tld/port")
assert r.status_code == 200
assert r.text == "answer from port 80\n"
def test_forwards_to_dualstack_network(docker_compose, nginxproxy):
r = nginxproxy.get("http://dualstack.nginx-proxy.tld")
assert r.status_code == 200
assert "Welcome to nginx!" in r.text
def test_dualstack_network_prefer_ipv6_config(docker_compose, nginxproxy):
conf = nginxproxy.get_conf().decode('ASCII')
assert "IPv4 address: 172.16.20.2 (ignored; reachable but IPv6 prefered)" in conf
assert "server [fd00:cafe:face:feed::2]:80;" in conf

View File

@ -0,0 +1,47 @@
version: "2"
networks:
ipv4net:
ipam:
config:
- subnet: 172.16.10.0/24
dualstacknet:
enable_ipv6: true
ipam:
config:
- subnet: 172.16.20.0/24
- subnet: fd00:cafe:face:feed::/64
services:
ipv4only:
image: web
expose:
- "80"
environment:
WEB_PORTS: 80
VIRTUAL_HOST: ipv4only.nginx-proxy.tld
networks:
ipv4net:
ipv4_address: 172.16.10.2
dualstack:
image: nginx:alpine
environment:
VIRTUAL_HOST: dualstack.nginx-proxy.tld
networks:
dualstacknet:
ipv4_address: 172.16.20.2
ipv6_address: fd00:cafe:face:feed::2
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
environment:
PREFER_IPV6_NETWORK: "true"
networks:
ipv4net:
ipv4_address: 172.16.10.3
dualstacknet:
ipv4_address: 172.16.20.3
ipv6_address: fd00:cafe:face:feed::3