diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 8a65d1949..9d4bd03ea 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -227,9 +227,6 @@ http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout **proxy-buffer-size:** Sets the size of the buffer used for [reading the first part of the response](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) received from the proxied server. This part usually contains a small response header.` -**resolver:** Configures name servers used to [resolve](http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) names of upstream servers into addresses - - **server-name-hash-max-size:** Sets the maximum size of the [server names hash tables](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size) used in server names, map directive’s values, MIME types, names of request header strings, etc. http://nginx.org/en/docs/hash.html diff --git a/controllers/nginx/pkg/template/configmap.go b/controllers/nginx/pkg/template/configmap.go index 09bce6d00..83adac4bb 100644 --- a/controllers/nginx/pkg/template/configmap.go +++ b/controllers/nginx/pkg/template/configmap.go @@ -31,6 +31,7 @@ import ( "k8s.io/ingress/controllers/nginx/pkg/config" "k8s.io/ingress/core/pkg/ingress/defaults" + "k8s.io/ingress/core/pkg/net/dns" ) const ( @@ -97,6 +98,13 @@ func ReadConfig(conf *api.ConfigMap) config.Configuration { if err != nil { glog.Infof("%v", err) } + + nss, err := dns.GetSystemNameServers() + if err != nil { + glog.Infof("unexpected error reading /etc/resolv.conf file: %v", err) + } + to.Resolver = nss + return to } diff --git a/controllers/nginx/pkg/template/template.go b/controllers/nginx/pkg/template/template.go index 59c6c844f..43b62d2bd 100644 --- a/controllers/nginx/pkg/template/template.go +++ b/controllers/nginx/pkg/template/template.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "net" "os/exec" "strings" text_template "text/template" @@ -29,6 +30,7 @@ import ( "k8s.io/ingress/controllers/nginx/pkg/config" "k8s.io/ingress/core/pkg/ingress" + ing_net "k8s.io/ingress/core/pkg/net" "k8s.io/ingress/core/pkg/watch" ) @@ -134,6 +136,7 @@ var ( "buildRateLimitZones": buildRateLimitZones, "buildRateLimit": buildRateLimit, "buildSSPassthroughUpstreams": buildSSPassthroughUpstreams, + "buildResolvers": buildResolvers, "contains": strings.Contains, "hasPrefix": strings.HasPrefix, @@ -143,6 +146,27 @@ var ( } ) +// buildResolvers returns the resolvers reading the /etc/resolv.conf file +func buildResolvers(a interface{}) string { + // NGINX need IPV6 addresses to be surrounded by brakets + nss := a.([]net.IP) + if len(nss) == 0 { + return "" + } + + r := []string{"resolver"} + for _, ns := range nss { + if ing_net.IsIPV6(ns) { + r = append(r, fmt.Sprintf("[%v]", ns)) + } else { + r = append(r, fmt.Sprintf("%v", ns)) + } + } + r = append(r, "valid=30s;") + + return strings.Join(r, " ") +} + func buildSSPassthroughUpstreams(b interface{}, sslb interface{}) string { backends := b.([]*ingress.Backend) sslBackends := sslb.([]*ingress.SSLPassthroughBackend) diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index ac3754b78..12f320c88 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -85,10 +85,7 @@ http { access_log /var/log/nginx/access.log upstreaminfo if=$loggable; error_log /var/log/nginx/error.log {{ $cfg.ErrorLogLevel }}; - {{ if not (empty $cfg.Resolver) }}# Custom dns resolver. - resolver {{ $cfg.Resolver }} valid=30s; - resolver_timeout 10s; - {{ end }} + {{ buildResolvers $cfg.Resolver }} {{/* Whenever nginx proxies a request without a "Connection" header, the "Connection" header is set to "close" */}} {{/* when making the target request. This means that you cannot simply use */}} diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index 29e889f6a..46679ee99 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -992,7 +992,7 @@ func (ic *GenericController) getEndpoints( } // check for invalid port value - if targetPort <=0 { + if targetPort <= 0 { continue } diff --git a/core/pkg/ingress/defaults/main.go b/core/pkg/ingress/defaults/main.go index 9c85ba404..bd90576a6 100644 --- a/core/pkg/ingress/defaults/main.go +++ b/core/pkg/ingress/defaults/main.go @@ -1,5 +1,7 @@ package defaults +import "net" + // Backend defines the mandatory configuration that an Ingress controller must provide // The reason of this requirements is the annotations are generic. If some implementation do not supports // one or more annotations it just can provides defaults @@ -30,9 +32,9 @@ type Backend struct { // http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size) ProxyBufferSize string `structs:"proxy-buffer-size"` - // Configures name servers used to resolve names of upstream servers into addresses - // http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver - Resolver string `structs:"resolver"` + // Name server/s used to resolve names of upstream servers into IP addresses. + // The file /etc/resolv.conf is used as DNS resolution configuration. + Resolver []net.IP // SkipAccessLogURLs sets a list of URLs that should not appear in the NGINX access log // This is useful with urls like `/health` or `health-check` that make "complex" reading the logs diff --git a/core/pkg/net/dns/dns.go b/core/pkg/net/dns/dns.go index 1988d987c..d381904e5 100644 --- a/core/pkg/net/dns/dns.go +++ b/core/pkg/net/dns/dns.go @@ -18,15 +18,18 @@ package dns import ( "io/ioutil" + "net" "strings" "github.com/golang/glog" ) +var defResolvConf = "/etc/resolv.conf" + // GetSystemNameServers returns the list of nameservers located in the file /etc/resolv.conf -func GetSystemNameServers() ([]string, error) { - var nameservers []string - file, err := ioutil.ReadFile("/etc/resolv.conf") +func GetSystemNameServers() ([]net.IP, error) { + var nameservers []net.IP + file, err := ioutil.ReadFile(defResolvConf) if err != nil { return nameservers, err } @@ -43,10 +46,13 @@ func GetSystemNameServers() ([]string, error) { continue } if fields[0] == "nameserver" { - nameservers = append(nameservers, fields[1:]...) + ip := net.ParseIP(fields[1]) + if ip != nil { + nameservers = append(nameservers, ip) + } } } - glog.V(3).Infof("nameservers to use: %v", nameservers) + glog.V(3).Infof("nameservers IP address/es to use: %v", nameservers) return nameservers, nil } diff --git a/core/pkg/net/dns/dns_test.go b/core/pkg/net/dns/dns_test.go index 488929e88..1b99da1d7 100644 --- a/core/pkg/net/dns/dns_test.go +++ b/core/pkg/net/dns/dns_test.go @@ -17,6 +17,9 @@ limitations under the License. package dns import ( + "io/ioutil" + "net" + "os" "testing" ) @@ -28,4 +31,31 @@ func TestGetDNSServers(t *testing.T) { if len(s) < 1 { t.Error("expected at least 1 nameserver in /etc/resolv.conf") } + + file, err := ioutil.TempFile("", "fw") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer file.Close() + defer os.Remove(file.Name()) + + ioutil.WriteFile(file.Name(), []byte(` + nameserver 2001:4860:4860::8844 + nameserver 2001:4860:4860::8888 + nameserver 8.8.8.8 + `), 0644) + + defResolvConf = file.Name() + s, err = GetSystemNameServers() + if err != nil { + t.Fatalf("unexpected error reading /etc/resolv.conf file: %v", err) + } + if len(s) < 3 { + t.Errorf("expected at 3 nameservers but %v returned", len(s)) + } + + eip := net.ParseIP("2001:4860:4860::8844") + if !s[0].Equal(eip) { + t.Errorf("expected %v as nameservers but %v returned", eip, s[0]) + } } diff --git a/core/pkg/net/net.go b/core/pkg/net/net.go new file mode 100644 index 000000000..cc4891aa5 --- /dev/null +++ b/core/pkg/net/net.go @@ -0,0 +1,30 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package net + +import ( + _net "net" + "strings" +) + +// IsIPV6 checks if the input contains a valid IPV6 address +func IsIPV6(ip _net.IP) bool { + if dp := strings.Index(ip.String(), ":"); dp != -1 { + return true + } + return false +} diff --git a/core/pkg/net/net_test.go b/core/pkg/net/net_test.go new file mode 100644 index 000000000..21b7bf5a9 --- /dev/null +++ b/core/pkg/net/net_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package net + +import ( + "net" + "testing" +) + +func TestIsIPV6(t *testing.T) { + tests := []struct { + in net.IP + isIPV6 bool + }{ + {net.ParseIP("2001:4860:4860::8844"), true}, + {net.ParseIP("2001:4860:4860::8888"), true}, + {net.ParseIP("0:0:0:0:0:ffff:c868:8165"), true}, + {net.ParseIP("2001:db8:85a3::8a2e:370:7334"), true}, + {net.ParseIP("::1"), true}, + {net.ParseIP("8.8.8.8"), false}, + } + + for _, test := range tests { + isIPV6 := IsIPV6(test.in) + if isIPV6 && !test.isIPV6 { + t.Fatalf("%v expected %v but returned %v", test.in, test.isIPV6, isIPV6) + } + } +}