diff --git a/Makefile b/Makefile index e65a1f2948..24905326df 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ IMAGE = $(REGISTRY)/$(IMGNAME) MULTI_ARCH_IMG = $(IMAGE)-$(ARCH) # Set default base image dynamically for each arch -BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.37 +BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.38 ifeq ($(ARCH),arm) QEMUARCH=arm diff --git a/images/nginx/Makefile b/images/nginx/Makefile index 615156ebb0..b5a7f1dd37 100644 --- a/images/nginx/Makefile +++ b/images/nginx/Makefile @@ -13,7 +13,7 @@ # limitations under the License. # 0.0.0 shouldn't clobber any released builds -TAG ?= 0.37 +TAG ?= 0.38 REGISTRY ?= quay.io/kubernetes-ingress-controller ARCH ?= $(shell go env GOARCH) DOCKER ?= gcloud docker -- diff --git a/images/nginx/build.sh b/images/nginx/build.sh index 687badc14a..c80bcc3453 100755 --- a/images/nginx/build.sh +++ b/images/nginx/build.sh @@ -19,7 +19,7 @@ set -o errexit set -o nounset set -o pipefail -export NGINX_VERSION=1.13.9 +export NGINX_VERSION=1.13.10 export NDK_VERSION=0.3.0 export VTS_VERSION=0.1.15 export SETMISC_VERSION=0.31 @@ -28,7 +28,7 @@ export MORE_HEADERS_VERSION=0.33 export NGINX_DIGEST_AUTH=274490cec649e7300fea97fed13d84e596bbc0ce export NGINX_SUBSTITUTIONS=bc58cb11844bc42735bbaef7085ea86ace46d05b export NGINX_OPENTRACING_VERSION=0.2.1 -export OPENTRACING_CPP_VERSION=1.2.0 +export OPENTRACING_CPP_VERSION=1.3.0 export ZIPKIN_CPP_VERSION=0.2.0 export JAEGER_VERSION=0.2.0 export MODSECURITY_VERSION=1.0.0 @@ -112,7 +112,7 @@ mkdir --verbose -p "$BUILD_PATH" cd "$BUILD_PATH" # download, verify and extract the source files -get_src 5faea18857516fe68d30be39c3032bd22ed9cf85e1a6fdf32e3721d96ff7fa42 \ +get_src 336182104d90be3c40c874f7f06f87dbb357da1dc74ea573ad081a0f29a94885 \ "http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz" get_src 88e05a99a8a7419066f5ae75966fb1efc409bad4522d14986da074554ae61619 \ @@ -139,7 +139,7 @@ get_src 618551948ab14cac51d6e4ad00452312c7b09938f59ebff4f93875013be31f2d \ get_src ce66acf943a604ef9a0bb477c7efca1fe583076991647aa646aa3d8804328364 \ "https://github.com/opentracing-contrib/nginx-opentracing/archive/v$NGINX_OPENTRACING_VERSION.tar.gz" -get_src c77041cb2f147ac81b2b0702abfced5565a9cebc318d045c060a4c3e074009ee \ +get_src 06dc5f9740d27dc4684399e491211be46a8069a10277f25513dadeb71199ce4c \ "https://github.com/opentracing/opentracing-cpp/archive/v$OPENTRACING_CPP_VERSION.tar.gz" get_src 611eb6a1ff1c326c472421ae2486ba34a94ddc78d90047df3f097bcdad3298e3 \ diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index 64d69281d6..ed18479227 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -32,6 +32,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" "k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend" + "k8s.io/ingress-nginx/internal/ingress/annotations/grpc" "k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" "k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing" @@ -91,6 +92,7 @@ type Ingress struct { XForwardedPrefix bool SSLCiphers string Logs log.Config + GRPC bool } // Extractor defines the annotation parsers to be used in the extraction of annotations @@ -130,6 +132,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "XForwardedPrefix": xforwardedprefix.NewParser(cfg), "SSLCiphers": sslcipher.NewParser(cfg), "Logs": log.NewParser(cfg), + "GRPC": grpc.NewParser(cfg), }, } } diff --git a/internal/ingress/annotations/grpc/main.go b/internal/ingress/annotations/grpc/main.go new file mode 100644 index 0000000000..98036ee6a9 --- /dev/null +++ b/internal/ingress/annotations/grpc/main.go @@ -0,0 +1,44 @@ +/* +Copyright 2016 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 grpc + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +type grpc struct { + r resolver.Resolver +} + +// NewParser creates a new gRPC annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return grpc{r} +} + +// ParseAnnotations parses the annotations contained in the ingress +// rule used to indicate if the Kubernetes service exposes gRPC +func (a grpc) Parse(ing *extensions.Ingress) (interface{}, error) { + if ing.GetAnnotations() == nil { + return false, ing_errors.ErrMissingAnnotations + } + + return parser.GetBoolAnnotation("grpc-backend", ing) +} diff --git a/internal/ingress/annotations/grpc/main_test.go b/internal/ingress/annotations/grpc/main_test.go new file mode 100644 index 0000000000..fccc1138ca --- /dev/null +++ b/internal/ingress/annotations/grpc/main_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 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 grpc + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" + + "k8s.io/apimachinery/pkg/util/intstr" +) + +func buildIngress() *extensions.Ingress { + return &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{ + Backend: &extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + }, + } +} + +func TestParseAnnotations(t *testing.T) { + ing := buildIngress() + + _, err := NewParser(&resolver.Mock{}).Parse(ing) + if err == nil { + t.Errorf("unexpected error: %v", err) + } + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("grpc-backend")] = "true" + ing.SetAnnotations(data) + // test ingress using the annotation without a TLS section + _, err = NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress with sslpassthrough") + } + + // test with a valid host + ing.Spec.TLS = []extensions.IngressTLS{ + { + Hosts: []string{"foo.bar.com"}, + }, + } + i, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("expected error parsing ingress with sslpassthrough") + } + val, ok := i.(bool) + if !ok { + t.Errorf("expected a bool type") + } + if !val { + t.Errorf("expected true but false returned") + } +} diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index 3fe0492c28..f3d133ed99 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -455,6 +455,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] loc.UsePortInRedirects = anns.UsePortInRedirects loc.Connection = anns.Connection loc.Logs = anns.Logs + loc.GRPC = anns.GRPC if loc.Redirect.FromToWWW { server.RedirectFromToWWW = true @@ -489,6 +490,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] UsePortInRedirects: anns.UsePortInRedirects, Connection: anns.Connection, Logs: anns.Logs, + GRPC: anns.GRPC, } if loc.Redirect.FromToWWW { @@ -923,6 +925,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress, defLoc.VtsFilterKey = anns.VtsFilterKey defLoc.Whitelist = anns.Whitelist defLoc.Denied = anns.Denied + defLoc.GRPC = anns.GRPC } } } diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index 787e27d779..d38eb377e6 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -324,6 +324,12 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur path := location.Path proto := "http" + proxyPass := "proxy_pass" + if location.GRPC { + proxyPass = "grpc_pass" + proto = "grpc" + } + upstreamName := "upstream_balancer" if !dynamicConfigurationEnabled { @@ -334,6 +340,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur if backend.Name == location.Backend { if backend.Secure || backend.SSLPassthrough { proto = "https" + if location.GRPC { + proto = "grpcs" + } } if !dynamicConfigurationEnabled && isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) { @@ -345,7 +354,7 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur } // defProxyPass returns the default proxy_pass, just the name of the upstream - defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, upstreamName) + defProxyPass := fmt.Sprintf("%v %s://%s;", proxyPass, proto, upstreamName) // if the path in the ingress rule is equals to the target: no special rewrite if path == location.Rewrite.Target { @@ -382,14 +391,14 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur return fmt.Sprintf(` rewrite %s(.*) /$1 break; rewrite %s / break; - %vproxy_pass %s://%s; - %v`, path, location.Path, xForwardedPrefix, proto, upstreamName, abu) + %v%v %s://%s; + %v`, path, location.Path, xForwardedPrefix, proxyPass, proto, upstreamName, abu) } return fmt.Sprintf(` rewrite %s(.*) %s/$1 break; - %vproxy_pass %s://%s; - %v`, path, location.Rewrite.Target, xForwardedPrefix, proto, upstreamName, abu) + %v%v %s://%s; + %v`, path, location.Rewrite.Target, xForwardedPrefix, proxyPass, proto, upstreamName, abu) } // default proxy_pass diff --git a/internal/ingress/types.go b/internal/ingress/types.go index 40c4452fa8..7653226f16 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -265,6 +265,9 @@ type Location struct { // Logs allows to enable or disable the nginx logs // By default this is enabled Logs log.Config `json:"logs,omitempty"` + // GRPC indicates if the kubernetes service exposes a gRPC interface + // By default this is false + GRPC bool `json:"logs,omitempty"` } // SSLPassthroughBackend describes a SSL upstream server configured diff --git a/internal/ingress/types_equals.go b/internal/ingress/types_equals.go index 333d3647fa..e262fe1a10 100644 --- a/internal/ingress/types_equals.go +++ b/internal/ingress/types_equals.go @@ -382,6 +382,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if !(&l1.Logs).Equal(&l2.Logs) { return false } + if l1.GRPC != l2.GRPC { + return false + } return true }