简而言之,faas-provider就是提供faas底层承载能力的系统的一个抽象,常见的比如kubernetes这种服务编排系统就能当作底层承载系统,也就是针对kubernetes实现一套faas-provider就能提供faas能力(这个实现就是faas-netes)。
faas-provider的作用
这个faas-provider可用于为OpenFaaS编写自己的后端(类似于kubenetes)。在使用Dockerfile构建镜像的过程中可以将Golang SDK嵌入到项目中,以便可以提供与OpenFaaS网关兼容的faas能力provider程序。
faas-provider为faas系统提供CRUD和函数调用的功能。如果开发者完成了所需的endpoint,那么将能够使用容器编排来作为faas的后端系统。也就是说openfaas指定了一套provider,实现了里面的端点,就能对接openfaas的框架。
faas-provider的模块
路由
因为provider最重要的功能就是连接gateway和faas底层能力集群,所以透传,或者路由是最重要的功能。faas-provider提供了http.Client实例,Serve
是他的关键路由方法,从中可以看到函数相关的接口。
func Serve(handlers *types.FaaSHandlers, config *types.FaaSConfig) {
if config.EnableBasicAuth {
reader := auth.ReadBasicAuthFromDisk{
SecretMountPath: config.SecretMountPath,
}
credentials, err := reader.Read()
if err != nil {
log.Fatal(err)
}
handlers.FunctionReader = auth.DecorateWithBasicAuth(handlers.FunctionReader, credentials)
handlers.DeployHandler = auth.DecorateWithBasicAuth(handlers.DeployHandler, credentials)
handlers.DeleteHandler = auth.DecorateWithBasicAuth(handlers.DeleteHandler, credentials)
handlers.UpdateHandler = auth.DecorateWithBasicAuth(handlers.UpdateHandler, credentials)
handlers.ReplicaReader = auth.DecorateWithBasicAuth(handlers.ReplicaReader, credentials)
handlers.ReplicaUpdater = auth.DecorateWithBasicAuth(handlers.ReplicaUpdater, credentials)
handlers.InfoHandler = auth.DecorateWithBasicAuth(handlers.InfoHandler, credentials)
handlers.SecretHandler = auth.DecorateWithBasicAuth(handlers.SecretHandler, credentials)
handlers.LogHandler = auth.DecorateWithBasicAuth(handlers.LogHandler, credentials)
}
// System (auth) endpoints
r.HandleFunc("/system/functions", handlers.FunctionReader).Methods("GET")
r.HandleFunc("/system/functions", handlers.DeployHandler).Methods("POST")
r.HandleFunc("/system/functions", handlers.DeleteHandler).Methods("DELETE")
r.HandleFunc("/system/functions", handlers.UpdateHandler).Methods("PUT")
r.HandleFunc("/system/function/{name:["+NameExpression+"]+}", handlers.ReplicaReader).Methods("GET")
r.HandleFunc("/system/scale-function/{name:["+NameExpression+"]+}", handlers.ReplicaUpdater).Methods("POST")
r.HandleFunc("/system/info", handlers.InfoHandler).Methods("GET")
r.HandleFunc("/system/secrets", handlers.SecretHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete)
r.HandleFunc("/system/logs", handlers.LogHandler).Methods(http.MethodGet)
r.HandleFunc("/system/namespaces", handlers.ListNamespaceHandler).Methods("GET")
// Open endpoints
r.HandleFunc("/function/{name:["+NameExpression+"]+}", handlers.FunctionProxy)
r.HandleFunc("/function/{name:["+NameExpression+"]+}/", handlers.FunctionProxy)
r.HandleFunc("/function/{name:["+NameExpression+"]+}/{params:.*}", handlers.FunctionProxy)
if handlers.HealthHandler != nil {
r.HandleFunc("/healthz", handlers.HealthHandler).Methods("GET")
}
readTimeout := config.ReadTimeout
writeTimeout := config.WriteTimeout
tcpPort := 8080
if config.TCPPort != nil {
tcpPort = *config.TCPPort
}
s := &http.Server{
Addr: fmt.Sprintf(":%d", tcpPort),
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes, // 1MB - can be overridden by setting Server.MaxHeaderBytes.
Handler: r,
}
log.Fatal(s.ListenAndServe())
}
实现者只要引入provider包,然后调用该Serve
方法,传入构造好的handlers
作为参数,就能够和gateway打通,比如kubernetes的provider(faas-netes)使用如下实现,
func makeApplyHandler(defaultNamespace string, client clientset.Interface) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
...
got, err := client.OpenfaasV1().Functions(namespace).Get(r.Context(), req.Service, opts)
...
if miss == false && got != nil {
...
//这里的Update方法就实现了对kubernetes中资源的更新
if _, err = client.OpenfaasV1().Functions(namespace).
Update(r.Context(), updated, metav1.UpdateOptions{}); err != nil {
...
}
}
}
}
bootstrapHandlers := types.FaaSHandlers{
...
DeployHandler: makeApplyHandler(functionNamespace, client),
...
}
func (s *Server) Start() {
bootstrap.Serve(s.BootstrapHandlers, s.BootstrapConfig)
}
faas如果是通过provider来代理请求(gateway中的属性direct_functions置为false),就会用到faas-provider中proxy包中的代理client。这个client具有如下的功能:
- 这些传输值确保http客户端超过设定值后,最终将超时,并防止无限次重试
- 重用连接,在高流量的情况下防止coreDns被速率限制
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
//用于创建http连接
DialContext: (&net.Dialer{
Timeout: timeout,
KeepAlive: 1 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: 120 * time.Millisecond,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1500 * time.Millisecond,
},
Timeout: timeout,
//禁止重定向,会在重试循环中使用到
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
这里的MaxIdleConns
和MaxIdleConnswPerHost
的默认值为1024,可用于调优HTTP代理性能。由于go默认的htt.client在请求完成后会马上关闭连接,所以为了链接复用,这个值可以被设置为希望在同一个ip上轮训的次数,这个值建议至少1k。
与之相反的如果不采用provider转发(direct_functions置为true),那么流量会直接转发的pod中,转发的url形如:http://base64.openfaas-fn.svc.cluster.local:8080
权限验证
provider会在磁盘的固定路径中查找到用户名和密码,
reader := auth.ReadBasicAuthFromDisk{
SecretMountPath: config.SecretMountPath,
}
credentials, err := reader.Read()
然后和请求头中的basicAuth信息作比对,从而达到权限认证的目的。
func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredentials) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, password, ok := r.BasicAuth()
const noMatch = 0
if !ok ||
user != credentials.User ||
subtle.ConstantTimeCompare([]byte(credentials.Password), []byte(password)) == noMatch {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("invalid credentials"))
return
}
next.ServeHTTP(w, r)
}
}
定义函数的属性
provider定义了函数部署和函数状态两种结构体,只要遵从这种定义结构,就能作为faas系统的provider提供severless能力。从源码中也可以看到该种类型的定义基本就是按照kunernetes对于集群中资源的定义。
type FunctionDeployment struct {
Service string `json:"service"`
Image string `json:"image"`
Network string `json:"network"`
EnvProcess string `json:"envProcess"`
EnvVars map[string]string `json:"envVars"`
RegistryAuth string `json:"registryAuth,omitempty"`
Constraints []string `json:"constraints"`
Secrets []string `json:"secrets"`
Labels *map[string]string `json:"labels"`
Annotations *map[string]string `json:"annotations"`
Limits *FunctionResources `json:"limits"`
Requests *FunctionResources `json:"requests"`
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem"`
Namespace string `json:"namespace,omitempty"`
}
type FunctionStatus struct {
Name string `json:"name"`
Image string `json:"image"`
InvocationCount float64 `json:"invocationCount"`
Replicas uint64 `json:"replicas"`
EnvProcess string `json:"envProcess"`
AvailableReplicas uint64 `json:"availableReplicas"`
Labels *map[string]string `json:"labels"`
Annotations *map[string]string `json:"annotations"`
Namespace string `json:"namespace,omitempty"`
}
FunctionStatus
会作为gateway模块中GetReplicas
方法的返回类型,用来描述函数的详细状态。
后面我们会讲到faas-provider的kubenetes的实现,也就是openfaas-netes。