From adf12fced1d9db98c767554adff1c7895e50ee24 Mon Sep 17 00:00:00 2001 From: Manuel Alejandro de Brito Fontes Date: Thu, 22 Mar 2018 00:38:47 -0300 Subject: [PATCH] Add support for gRPC (#2223) * Update nginx to 1.13.10 and enable gRPC * Add support for grpc --- Makefile | 2 +- internal/ingress/annotations/annotations.go | 3 + internal/ingress/annotations/grpc/main.go | 44 ++++++++++ .../ingress/annotations/grpc/main_test.go | 80 +++++++++++++++++++ internal/ingress/controller/controller.go | 3 + .../ingress/controller/template/template.go | 19 +++-- internal/ingress/types.go | 3 + internal/ingress/types_equals.go | 3 + 8 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 internal/ingress/annotations/grpc/main.go create mode 100644 internal/ingress/annotations/grpc/main_test.go 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/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 }