faas-provider是一个模板,只要实现了这个模板的接口,就可以实现自己的provider。
faas-provider
OpenFaaS官方提供了两套后台provider:
- Docker Swarm
- Kubernetes
这两者在部署和调用函数的时候流程图如下:
部署一个函数
调用一个函数
provider要提供的一些API有:
- List / Create / Delete 一个函数
/system/functions
方法: GET / POST / DELETE
- 获取一个函数
/system/function/{name:[-a-zA-Z_0-9]+}
方法: GET
- 伸缩一个函数
/system/scale-function/{name:[-a-zA-Z_0-9]+}
方法: POST
- 调用一个函数
/function/{name:[-a-zA-Z_0-9]+}
方法: POST
在provider的server.go的serve方法,可以看到这个serve方法有一个参数是FaaSHandler对象,并创建了如下几个路由。
// Serve load your handlers into the correct OpenFaaS route spec. This function is blocking.
func Serve(handlers *types.FaaSHandlers, config *types.FaaSConfig) {
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:[-a-zA-Z_0-9]+}", handlers.ReplicaReader).Methods("GET")
r.HandleFunc("/system/scale-function/{name:[-a-zA-Z_0-9]+}", handlers.ReplicaUpdater).Methods("POST")
r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", handlers.FunctionProxy)
r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", handlers.FunctionProxy)
r.HandleFunc("/system/info", handlers.InfoHandler).Methods("GET")
if config.EnableHealth {
r.HandleFunc("/healthz", handlers.Health).Methods("GET")
}
// 省略
}
因此如果要自己实现一个provider,只需实现FaaSHandlers中的几个路由处理函数即可。这几个handler是:
// FaaSHandlers provide handlers for OpenFaaS
type FaaSHandlers struct {
FunctionReader http.HandlerFunc
DeployHandler http.HandlerFunc
DeleteHandler http.HandlerFunc
ReplicaReader http.HandlerFunc
FunctionProxy http.HandlerFunc
ReplicaUpdater http.HandlerFunc
// Optional: Update an existing function
UpdateHandler http.HandlerFunc
Health http.HandlerFunc
InfoHandler http.HandlerFunc
}
我们以官方实现的faas-netes为例,讲解一下这几个hander的实现过程。
faas-netes
我们看下在faas-netes的中的FaaSHandlers实现:
bootstrapHandlers := bootTypes.FaaSHandlers{
FunctionProxy: handlers.MakeProxy(functionNamespace, cfg.ReadTimeout),
DeleteHandler: handlers.MakeDeleteHandler(functionNamespace, clientset),
DeployHandler: handlers.MakeDeployHandler(functionNamespace, clientset, deployConfig),
FunctionReader: handlers.MakeFunctionReader(functionNamespace, clientset),
ReplicaReader: handlers.MakeReplicaReader(functionNamespace, clientset),
ReplicaUpdater: handlers.MakeReplicaUpdater(functionNamespace, clientset),
UpdateHandler: handlers.MakeUpdateHandler(functionNamespace, clientset),
Health: handlers.MakeHealthHandler(),
InfoHandler: handlers.MakeInfoHandler(version.BuildVersion(), version.GitCommit),
}
因为是Kubernetes上的provider实现,所以这些函数都有一个namespace的参数。
FunctionProxy
这里最重要的就是FunctionProxy,它主要负责调用函数。这个handler起到了一个代理转发的作用。
- 创建一个http的client对象
- 只处理get和post请求。
- 组装代理转发的watchdog的地址
url := forwardReq.ToURL(fmt.Sprintf("%s.%s", service, functionNamespace), watchdogPort)
所以最后请求的格式就会形如:
http://函数名.namespace:监视器的端口/路径 - 将请求发出去
- 设置http响应的头
ReplicaReader和ReplicaUpdater
这两个是和副本数相关的,所以放在一起对比讲解。这两个的实现依赖于Kubernetes的客户端,获取代码如下:
clientset, err := kubernetes.NewForConfig(config)
这个config主要满足以下几个条件就行:
Config{
// TODO: switch to using cluster DNS.
Host: "https://" + net.JoinHostPort(host, port),
BearerToken: string(token),
TLSClientConfig: tlsClientConfig,
}
Kubernetes的所有操作都可以通过rest api来完成,这两个handler也是通过调用Kubernetes的api来做的。
ReplicaReader
MakeReplicaReader函数是获取当前的副本数:
- 通过mux从路由中获取到name参数
- 调用getService方法获取副本数,getService的核心代码就一句:
item, err := clientset.ExtensionsV1beta1().Deployments(functionNamespace).Get(functionName, getOpts) - 序列化之后,把结果返回
ReplicaUpdater
MakeReplicaUpdater是解析从gateway传过来的post请求,调用k8s的API设置副本数。
- 从请求中取出body
- 首先获取该函数的已部署的deployment对象
- 然后将deployment的副本数量设置为应设数量,这样做的目的是为了仅仅修改副本数,而不修改别的属性。
_, err = clientset.ExtensionsV1beta1().Deployments(functionNamespace).Update(deployment)
注:mux做路由的时候,如果成功的时候不对w做任何处理,是会默认状态码为200,空字符串。
DeleteHandler,DeployHandler,FunctionReader和UpdateHandler
这几个都是对函数的操作,其实就是调用一下Kubernetes的API进行操作。
这几个是核心的几句代码:
clientset.ExtensionsV1beta1().Deployments(functionNamespace).Delete(request.FunctionName, opts)
deploy := clientset.Extensions().Deployments(functionNamespace)
res, err := clientset.ExtensionsV1beta1().Deployments(functionNamespace).List(listOpts)
_, updateErr := clientset.CoreV1().Services(functionNamespace).Update(service)
总结
官方还提供了一个faas-swarm,其实现思路也是这样,通过swarm的api来做对容器的操作。至于如何调用一个函数,都是在函数的watchdog中实现。