From 6e523ca51a89657a3446725b73e6b258d1a05f42 Mon Sep 17 00:00:00 2001 From: "Alex Ellis (OpenFaaS Ltd)" Date: Tue, 11 Feb 2020 09:36:39 +0000 Subject: [PATCH] Create bypass entries in function namespace * Documents installation / approach with example for nodeinfo * Adds feature and unit tests * Remove armhf since it can be installed via the openfaas chart using k3sup * Update release version to latest * Update examples * Remove certifier from PR template Tested e2e with Nginx on Civo's k3s service with NginxIngress and traefik removed. Signed-off-by: Alex Ellis (OpenFaaS Ltd) --- .github/PULL_REQUEST_TEMPLATE.md | 1 - LICENSE | 2 +- README.md | 35 ++++++++++++-- artifacts/nodeinfo-bypass-ingress.yaml | 11 +++++ artifacts/operator-amd64.yaml | 10 ++-- artifacts/operator-armhf.yaml | 37 --------------- artifacts/operator-rbac.yaml | 2 +- go.mod | 1 + go.sum | 2 + main.go | 5 +- pkg/controller/controller.go | 41 ++++++++++------- pkg/controller/controller_test.go | 64 ++++++++++++++++++++++++++ 12 files changed, 145 insertions(+), 66 deletions(-) create mode 100644 artifacts/nodeinfo-bypass-ingress.yaml delete mode 100644 artifacts/operator-armhf.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4045c8c5..f3fdc896 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,7 +13,6 @@ -- [ ] I have run the [certifier](https://github.com/openfaas/certifier) ## Types of changes diff --git a/LICENSE b/LICENSE index 7bb7d037..3804aa46 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017-2019 OpenFaaS Authors +Copyright (c) 2017-2019 OpenFaaS Author(s) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2e69ee02..0925c485 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,35 @@ If you are confident in the configuration, switch over to the production issuer, Save the file and apply. +### Bypass mode + +The IngressOperator can be used to create Ingress records that bypass the OpenFaaS Gateway. This may be useful when you are running a non-standard workload such as a brownfields monolith to reduce hops, or with an unsupported protocol like gRPC or websockets. + +Example: + +```yaml +apiVersion: openfaas.com/v1alpha2 +kind: FunctionIngress +metadata: + name: nodeinfo + namespace: openfaas-fn +spec: + domain: "nodeinfo.myfaas.club" + function: "nodeinfo" + ingressType: "nginx" + bypassGateway: true +``` + +Note that since Ingress records must be created in the same namespace as the backend service, `namespace` is changed to `openfaas-fn`. + +By default, the OpenFaaS helm chart can deploy the first instance of the operator, if you need gateway bypass, then deploy a second operator using a customised version of `artifacts/operator-amd64.yaml`. + +When deploying the operator, you will also need to: + +* Set the `ingress_namespace` env-var to `openfaas-fn` +* Edit the deployment `namespace` to `openfaas-fn` +* Optionally: edit `artifacts/operator-rbac.yaml` to `openfaas-fn` and apply + ### Run or deploy the IngressOperator #### In-cluster: @@ -328,9 +357,9 @@ This project follows the [OpenFaaS contributing guide](./CONTRIBUTING.md) ## Configuration via Environment Variable -| Option | Usage | -|---------------------|-------------------------------------------------------------------------------------------------| -| `openfaas_gateway_namespace` | Namespace for the OpenFaaS gateway, default: `openfaas` | +| Option | Usage | +|---------------------|----------------------------------------------------------------------------------------------------| +| `ingress_namespace` | Namespace to create Ingress within, if bypassing gateway, set to `openfaas-fn`. default: `openfaas`| ## LICENSE diff --git a/artifacts/nodeinfo-bypass-ingress.yaml b/artifacts/nodeinfo-bypass-ingress.yaml new file mode 100644 index 00000000..a54461f1 --- /dev/null +++ b/artifacts/nodeinfo-bypass-ingress.yaml @@ -0,0 +1,11 @@ +apiVersion: openfaas.com/v1alpha2 +kind: FunctionIngress +metadata: + name: nodeinfo + namespace: openfaas-fn +spec: + domain: "nodeinfo.myfaas.club" + function: "nodeinfo" + ingressType: "nginx" + bypassGateway: true + path: / diff --git a/artifacts/operator-amd64.yaml b/artifacts/operator-amd64.yaml index e244c490..a71bd3fe 100644 --- a/artifacts/operator-amd64.yaml +++ b/artifacts/operator-amd64.yaml @@ -1,5 +1,5 @@ --- -apiVersion: apps/v1beta2 +apiVersion: apps/v1 kind: Deployment metadata: name: ingress-operator @@ -14,21 +14,19 @@ spec: labels: app: ingress-operator annotations: - prometheus.io.scrape: 'true' + prometheus.io.scrape: 'false' spec: serviceAccountName: ingress-operator containers: - name: operator - image: openfaas/ingress-operator:0.4.1 + image: openfaas/ingress-operator:0.6.0 imagePullPolicy: Always command: - ./ingress-operator - -logtostderr - -v=2 env: - - name: core_namespace - value: openfaas - - name: functions_namespace + - name: ingress_namespace value: openfaas-fn resources: limits: diff --git a/artifacts/operator-armhf.yaml b/artifacts/operator-armhf.yaml deleted file mode 100644 index 68af9faf..00000000 --- a/artifacts/operator-armhf.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -apiVersion: apps/v1beta2 -kind: Deployment -metadata: - name: ingress-operator - namespace: openfaas -spec: - replicas: 1 - selector: - matchLabels: - app: ingress-operator - template: - metadata: - labels: - app: ingress-operator - annotations: - prometheus.io.scrape: 'true' - spec: - serviceAccountName: ingress-operator - containers: - - name: operator - image: openfaas/ingress-operator:0.4.0-armhf - imagePullPolicy: Always - command: - - ./ingress-operator - - -logtostderr - - -v=2 - env: - - name: core_namespace - value: openfaas - - name: functions_namespace - value: openfaas-fn - resources: - limits: - memory: 128Mi - requests: - memory: 25Mi diff --git a/artifacts/operator-rbac.yaml b/artifacts/operator-rbac.yaml index c5b53126..c3c52f34 100644 --- a/artifacts/operator-rbac.yaml +++ b/artifacts/operator-rbac.yaml @@ -14,7 +14,7 @@ rules: - apiGroups: ["openfaas.com"] resources: ["functioningresses"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] -- apiGroups: ["extensions"] +- apiGroups: ["extensions", "networking"] resources: ["ingresses"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - apiGroups: [""] diff --git a/go.mod b/go.mod index def7806e..a56d328f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/emicklei/go-restful v2.11.1+incompatible // indirect github.com/go-openapi/spec v0.19.6 // indirect github.com/go-openapi/swag v0.19.7 // indirect + github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 // indirect github.com/google/go-cmp v0.3.0 github.com/googleapis/gnostic v0.2.0 // indirect diff --git a/go.sum b/go.sum index b0444c25..cd6cca9a 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,8 @@ github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1 github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/main.go b/main.go index 8137b9b8..5e497a41 100644 --- a/main.go +++ b/main.go @@ -36,10 +36,13 @@ var pullPolicyOptions = map[string]bool{ func init() { flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") + + // TODO: remove flag.Bool("logtostderr", false, "logtostderrĀ legacy flag") } func main() { + // TODO: remove flag.Set("logtostderr", "true") flag.Parse() @@ -67,7 +70,7 @@ func main() { } ingressNamespace := "openfaas" - if namespace, exists := os.LookupEnv("openfaas_gateway_namespace"); exists { + if namespace, exists := os.LookupEnv("ingress_namespace"); exists { ingressNamespace = namespace } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index dc9b1f49..448a470b 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -36,6 +36,7 @@ import ( const controllerAgentName = "ingress-operator" const faasIngressKind = "FunctionIngress" +const openfaasWorkloadPort = 8080 const ( // SuccessSynced is used as part of the Event 'reason' when a Function is synced @@ -419,6 +420,10 @@ func (c *Controller) handleObject(obj interface{}) { func makeRules(fni *faasv1.FunctionIngress) []v1beta1.IngressRule { path := "/(.*)" + if fni.Spec.BypassGateway { + path = "/" + } + if len(fni.Spec.Path) > 0 { path = fni.Spec.Path } @@ -427,6 +432,11 @@ func makeRules(fni *faasv1.FunctionIngress) []v1beta1.IngressRule { path = strings.TrimRight(path, "(.*)") } + serviceHost := "gateway" + if fni.Spec.BypassGateway { + serviceHost = fni.Spec.Function + } + return []v1beta1.IngressRule{ v1beta1.IngressRule{ Host: fni.Spec.Domain, @@ -436,9 +446,9 @@ func makeRules(fni *faasv1.FunctionIngress) []v1beta1.IngressRule { v1beta1.HTTPIngressPath{ Path: path, Backend: v1beta1.IngressBackend{ - ServiceName: "gateway", + ServiceName: serviceHost, ServicePort: intstr.IntOrString{ - IntVal: 8080, + IntVal: openfaasWorkloadPort, }, }, }, @@ -496,20 +506,19 @@ func makeAnnotations(function *faasv1.FunctionIngress) map[string]string { annotations["kubernetes.io/ingress.class"] = class annotations["com.openfaas.spec"] = string(specJSON) - switch class { - - case "nginx": - annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/function/" + function.Spec.Function + "/$1" - break - case "skipper": - annotations["zalando.org/skipper-filter"] = `setPath("/function/` + function.Spec.Function + `")` - break - - case "traefik": - annotations["traefik.ingress.kubernetes.io/rewrite-target"] = "/function/" + function.Spec.Function + "/$1" - annotations["traefik.ingress.kubernetes.io/rule-type"] = `PathPrefix` - - break + if !function.Spec.BypassGateway { + switch class { + case "nginx": + annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/function/" + function.Spec.Function + "/$1" + break + case "skipper": + annotations["zalando.org/skipper-filter"] = `setPath("/function/` + function.Spec.Function + `")` + break + case "traefik": + annotations["traefik.ingress.kubernetes.io/rewrite-target"] = "/function/" + function.Spec.Function + "/$1" + annotations["traefik.ingress.kubernetes.io/rule-type"] = `PathPrefix` + break + } } if function.Spec.UseTLS() { diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index ae7f51d2..fa157a54 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -48,6 +48,29 @@ func TestMakeAnnotations_IngressClass(t *testing.T) { } } +func TestMakeAnnotations_ByPassRemovesRewriteTarget(t *testing.T) { + wantIngressType := "nginx" + ingress := faasv1.FunctionIngress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "nginx", + }, + }, + Spec: faasv1.FunctionIngressSpec{ + IngressType: wantIngressType, + Function: "nodeinfo", + BypassGateway: true, + Domain: "nodeinfo.example.com", + }, + } + + result := makeAnnotations(&ingress) + t.Log(result) + if val, ok := result["nginx.ingress.kubernetes.io/rewrite-target"]; ok { + t.Errorf("No rewrite annotations should be given, but got: %s", val) + } +} + func TestMakeAnnotations_IngressClassAdditionalAnnotations(t *testing.T) { defaultRewriteAnnotation := "nginx.ingress.kubernetes.io/rewrite-target" ingress := faasv1.FunctionIngress{ @@ -89,6 +112,47 @@ func Test_makeRules_Nginx_RootPath_HasRegex(t *testing.T) { if gotPath != wantPath { t.Errorf("want path %s, but got %s", wantPath, gotPath) } + + gotPort := rules[0].HTTP.Paths[0].Backend.ServicePort + + if gotPort.IntValue() != openfaasWorkloadPort { + t.Errorf("want port %d, but got %d", openfaasWorkloadPort, gotPort.IntValue()) + } +} + +func Test_makeRules_Nginx_RootPath_IsRootWithBypassMode(t *testing.T) { + wantFunction := "nodeinfo" + ingress := faasv1.FunctionIngress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + Spec: faasv1.FunctionIngressSpec{ + BypassGateway: true, + IngressType: "nginx", + Function: "nodeinfo", + // Path: "/", + }, + } + + rules := makeRules(&ingress) + + if len(rules) == 0 { + t.Errorf("Ingress should give at least one rule") + t.Fail() + } + + wantPath := "/" + gotPath := rules[0].HTTP.Paths[0].Path + + if gotPath != wantPath { + t.Errorf("want path %s, but got %s", wantPath, gotPath) + } + + gotHost := rules[0].HTTP.Paths[0].Backend.ServiceName + + if gotHost != wantFunction { + t.Errorf("want host to be function: %s, but got %s", wantFunction, gotHost) + } } func Test_makeRules_Nginx_PathOverride(t *testing.T) {