mirror of
https://github.com/openfaas/faas.git
synced 2025-06-08 16:26:47 +00:00
Add direct_functions mode to gateway for tuning
Adds a pair of configuration options for performance tuning. The gateway can now invoke functions directly and can bypass the provider. See updated table in README.md for configuration values. BaseURLResolver is added with unit tests that decouples resolving upstream URL from the reverse proxy client code. - SingleHostBaseURLResolver resolves a single upstream host - FunctionAsHostBaseURLResolver resolves host based upon conventions within the URL of the request to a function for direct access Tested with Kubernetes (faas-netes) and faas-swarm through UI, CLI calling system endpoints and functions directly. Signed-off-by: Alex Ellis (VMware) <alexellis2@gmail.com>
This commit is contained in:
parent
a841e3d7f3
commit
0c7e59fe8a
@ -14,6 +14,8 @@ services:
|
||||
dnsrr: "true" # Temporarily use dnsrr in place of VIP while issue persists on PWD
|
||||
faas_nats_address: "nats"
|
||||
faas_nats_port: 4222
|
||||
direct_functions: "true" # Functions are invoked directly over the overlay network
|
||||
direct_functions_suffix: ""
|
||||
deploy:
|
||||
resources:
|
||||
# limits: # Enable if you want to limit memory usage
|
||||
|
@ -1,10 +1,11 @@
|
||||
FROM golang:1.9.4 as build
|
||||
WORKDIR /go/src/github.com/openfaas/faas/gateway
|
||||
|
||||
RUN curl -sL https://github.com/alexellis/license-check/releases/download/0.2.2/license-check \
|
||||
> /usr/bin/license-check \
|
||||
&& chmod +x /usr/bin/license-check
|
||||
|
||||
WORKDIR /go/src/github.com/openfaas/faas/gateway
|
||||
|
||||
|
||||
COPY vendor vendor
|
||||
|
||||
COPY handlers handlers
|
||||
|
@ -9,7 +9,7 @@ In summary:
|
||||
* UI built-in
|
||||
* Deploy your own functions or from the Function Store
|
||||
* Instrumentation via Prometheus
|
||||
* Autoscaling via AlertManager
|
||||
* Auto-scaling via AlertManager
|
||||
* REST API available
|
||||
|
||||

|
||||
@ -48,7 +48,9 @@ The gateway can be configured through the following environment variables:
|
||||
| `write_timeout` | HTTP timeout for writing a response body from your function (in seconds). Default: `8` |
|
||||
| `read_timeout` | HTTP timeout for reading the payload from the client caller (in seconds). Default: `8` |
|
||||
| `functions_provider_url` | URL of upstream [functions provider](https://github.com/openfaas/faas-provider/) - i.e. Swarm, Kubernetes, Nomad etc |
|
||||
| `faas_nats_address` | Address of NATS service. Required for asynchronous mode. |
|
||||
| `faas_nats_port` | Port for NATS service. Requrired for asynchronous mode. |
|
||||
| `faas_prometheus_host` | Host to connect to Prometheus. Default: `"prometheus"`. |
|
||||
| `faas_promethus_port` | Port to connect to Prometheus. Default: `9090`. |
|
||||
| `faas_nats_address` | Address of NATS service. Required for asynchronous mode |
|
||||
| `faas_nats_port` | Port for NATS service. Requrired for asynchronous mode |
|
||||
| `faas_prometheus_host` | Host to connect to Prometheus. Default: `"prometheus"` |
|
||||
| `faas_promethus_port` | Port to connect to Prometheus. Default: `9090` |
|
||||
| `direct_functions` | `true` or `false` - functions are invoked directly over overlay network without passing through provider |
|
||||
| `direct_functions_suffix` | Provide a DNS suffix for invoking functions directly over overlay network |
|
||||
|
55
gateway/handlers/baseurlresolver_test.go
Normal file
55
gateway/handlers/baseurlresolver_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSingleHostBaseURLResolver(t *testing.T) {
|
||||
|
||||
urlVal, _ := url.Parse("http://upstream:8080/")
|
||||
r := SingleHostBaseURLResolver{BaseURL: urlVal.String()}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
|
||||
|
||||
resolved := r.Resolve(req)
|
||||
want := "http://upstream:8080"
|
||||
if resolved != want {
|
||||
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
const watchdogPort = 8080
|
||||
|
||||
func TestFunctionAsHostBaseURLResolver_WithSuffix(t *testing.T) {
|
||||
suffix := "openfaas-fn.local.cluster.svc."
|
||||
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
|
||||
|
||||
resolved := r.Resolve(req)
|
||||
want := fmt.Sprintf("http://hello.%s:%d", suffix, watchdogPort)
|
||||
|
||||
if resolved != want {
|
||||
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionAsHostBaseURLResolver_WithoutSuffix(t *testing.T) {
|
||||
suffix := ""
|
||||
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
|
||||
|
||||
resolved := r.Resolve(req)
|
||||
want := fmt.Sprintf("http://hello%s:%d", suffix, watchdogPort)
|
||||
|
||||
if resolved != want {
|
||||
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@ -14,20 +15,22 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// HTTPNotifier notify about HTTP request/response
|
||||
type HTTPNotifier interface {
|
||||
Notify(method string, URL string, statusCode int, duration time.Duration)
|
||||
}
|
||||
|
||||
// BaseURLResolver URL resolver for upstream requests
|
||||
type BaseURLResolver interface {
|
||||
Resolve(r *http.Request) string
|
||||
}
|
||||
|
||||
// MakeForwardingProxyHandler create a handler which forwards HTTP requests
|
||||
func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, notifiers []HTTPNotifier) http.HandlerFunc {
|
||||
baseURL := proxy.BaseURL.String()
|
||||
if strings.HasSuffix(baseURL, "/") {
|
||||
baseURL = baseURL[0 : len(baseURL)-1]
|
||||
}
|
||||
|
||||
func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy, notifiers []HTTPNotifier, baseURLResolver BaseURLResolver) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
baseURL := baseURLResolver.Resolve(r)
|
||||
|
||||
requestURL := r.URL.String()
|
||||
requestURL := r.URL.Path
|
||||
|
||||
start := time.Now()
|
||||
|
||||
@ -90,10 +93,12 @@ func copyHeaders(destination http.Header, source *http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// PrometheusFunctionNotifier records metrics to Prometheus
|
||||
type PrometheusFunctionNotifier struct {
|
||||
Metrics *metrics.MetricOptions
|
||||
}
|
||||
|
||||
// Notify records metrics in Prometheus
|
||||
func (p PrometheusFunctionNotifier) Notify(method string, URL string, statusCode int, duration time.Duration) {
|
||||
seconds := duration.Seconds()
|
||||
serviceName := getServiceName(URL)
|
||||
@ -112,19 +117,52 @@ func (p PrometheusFunctionNotifier) Notify(method string, URL string, statusCode
|
||||
func getServiceName(urlValue string) string {
|
||||
var serviceName string
|
||||
forward := "/function/"
|
||||
if startsWith(urlValue, forward) {
|
||||
if strings.HasPrefix(urlValue, forward) {
|
||||
serviceName = urlValue[len(forward):]
|
||||
}
|
||||
return serviceName
|
||||
}
|
||||
|
||||
func startsWith(value, token string) bool {
|
||||
return len(value) > len(token) && strings.Index(value, token) == 0
|
||||
}
|
||||
|
||||
// LoggingNotifier notifies a log about a request
|
||||
type LoggingNotifier struct {
|
||||
}
|
||||
|
||||
// Notify a log about a request
|
||||
func (LoggingNotifier) Notify(method string, URL string, statusCode int, duration time.Duration) {
|
||||
log.Printf("Forwarded [%s] to %s - [%d] - %f seconds", method, URL, statusCode, duration.Seconds())
|
||||
}
|
||||
|
||||
// SingleHostBaseURLResolver resolves URLs against a single BaseURL
|
||||
type SingleHostBaseURLResolver struct {
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// Resolve the base URL for a request
|
||||
func (s SingleHostBaseURLResolver) Resolve(r *http.Request) string {
|
||||
|
||||
baseURL := s.BaseURL
|
||||
|
||||
if strings.HasSuffix(baseURL, "/") {
|
||||
baseURL = baseURL[0 : len(baseURL)-1]
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
|
||||
// FunctionAsHostBaseURLResolver resolves URLs using a function from the URL as a host
|
||||
type FunctionAsHostBaseURLResolver struct {
|
||||
FunctionSuffix string
|
||||
}
|
||||
|
||||
// Resolve the base URL for a request
|
||||
func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string {
|
||||
|
||||
svcName := getServiceName(r.URL.Path)
|
||||
|
||||
const watchdogPort = 8080
|
||||
var suffix string
|
||||
if len(f.FunctionSuffix) > 0 {
|
||||
suffix = "." + f.FunctionSuffix
|
||||
}
|
||||
|
||||
return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort)
|
||||
}
|
||||
|
@ -49,14 +49,23 @@ func main() {
|
||||
functionNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier}
|
||||
forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier}
|
||||
|
||||
faasHandlers.Proxy = handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers)
|
||||
faasHandlers.RoutelessProxy = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
faasHandlers.ListFunctions = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
urlResolver := handlers.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
|
||||
var functionURLResolver handlers.BaseURLResolver
|
||||
|
||||
queryFunction := handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers)
|
||||
if config.DirectFunctions {
|
||||
functionURLResolver = handlers.FunctionAsHostBaseURLResolver{FunctionSuffix: config.DirectFunctionsSuffix}
|
||||
} else {
|
||||
functionURLResolver = urlResolver
|
||||
}
|
||||
|
||||
faasHandlers.Proxy = handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver)
|
||||
|
||||
faasHandlers.RoutelessProxy = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
faasHandlers.ListFunctions = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
faasHandlers.DeployFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
faasHandlers.DeleteFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
faasHandlers.UpdateFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
queryFunction := handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver)
|
||||
|
||||
alertHandler := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL)
|
||||
faasHandlers.Alert = handlers.MakeAlertHandler(alertHandler)
|
||||
|
@ -38,6 +38,36 @@ func TestRead_UseExternalProvider_Defaults(t *testing.T) {
|
||||
t.Log("Default for UseExternalProvider should be false")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if config.DirectFunctions != false {
|
||||
t.Log("Default for DirectFunctions should be false")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if len(config.DirectFunctionsSuffix) > 0 {
|
||||
t.Log("Default for DirectFunctionsSuffix should be empty as a default")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead_DirectFunctionsOverride(t *testing.T) {
|
||||
defaults := NewEnvBucket()
|
||||
readConfig := types.ReadConfig{}
|
||||
defaults.Setenv("direct_functions", "true")
|
||||
wantSuffix := "openfaas-fn.cluster.local.svc."
|
||||
defaults.Setenv("direct_functions_suffix", wantSuffix)
|
||||
|
||||
config := readConfig.Read(defaults)
|
||||
|
||||
if config.DirectFunctions != true {
|
||||
t.Logf("DirectFunctions should be true, got: %v", config.DirectFunctions)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if config.DirectFunctionsSuffix != wantSuffix {
|
||||
t.Logf("DirectFunctionsSuffix want: %s, got: %s", wantSuffix, config.DirectFunctionsSuffix)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead_EmptyTimeoutConfig(t *testing.T) {
|
||||
|
@ -102,6 +102,9 @@ func (ReadConfig) Read(hasEnv HasEnv) GatewayConfig {
|
||||
cfg.PrometheusHost = prometheusHost
|
||||
}
|
||||
|
||||
cfg.DirectFunctions = parseBoolValue(hasEnv.Getenv("direct_functions"))
|
||||
cfg.DirectFunctionsSuffix = hasEnv.Getenv("direct_functions_suffix")
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
@ -131,6 +134,12 @@ type GatewayConfig struct {
|
||||
|
||||
// Port to connect to Prometheus.
|
||||
PrometheusPort int
|
||||
|
||||
// If set to true we will access upstream functions directly rather than through the upstream provider
|
||||
DirectFunctions bool
|
||||
|
||||
// If set this will be used to resolve functions directly
|
||||
DirectFunctionsSuffix string
|
||||
}
|
||||
|
||||
// UseNATS Use NATSor not
|
||||
|
Loading…
x
Reference in New Issue
Block a user