faas-netes是openfaas-provider的kubenetes的一个实现。在openfaas的架构中,他和Gateway存在于同一个pod中。
faas-netes的作用
我们可以看到在gateway的pod中有两个容器,一个是gateway,另一个就是openfaas的faas-netes。
faas-netes是一个OpenFaaS提供程序(provider),它为Kubernetes启用了OpenFaaS,提供获取,缩放,调用函数的功能。现有的REST API,CLI和UI完全兼容。faas-netes具有operator模式,因此可以使用kubectl来管理function。
faas-netes源码分析
首先使用kubeconfig构造了kubeclient,由于是使用kubeconfig文件来构造client,所以这个provider程序其实也能部署在集群之外。
clientCmdConfig, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
...
faasClient, err := clientset.NewForConfig(clientCmdConfig)
然后定义了InformerFactory,这里的Shared的意思是多个listeners共享同一个cache,并且kubernetes资源的变化会同时通知到cache和listeners。cache是client-go中对于kubernetes资源的线程安全的抽象信息缓存,listerners指的就是OnAdd、OnUpdate、OnDelete这些资源回调函数背后的监听者。
Informer 是 client-go 中的核心工具包,被 kubernetes 中众多组件所使用。Informer这种机制大大缓解了apiserver的压力。在openfaas架构中使用了这种机制,也是想更加高性能的贴合到kubernetes的架构中。
faasInformerFactory := informers.NewSharedInformerFactoryWithOptions(faasClient, defaultResync, faasInformerOpt)
...
go faasInformerFactory.Start(stopCh)//stopch是关机信号
根据faas-netes启动参数不同,会有下面两种模式的实现。
controller模式和operator模式
这两种模式都会启动kubenetes和faas自己的informer,并且监听endpoint来在函数代理调用的时候更加快速的解析出地址。
kubeInformerFactory := setup.kubeInformerFactory
faasInformerFactory := setup.faasInformerFactory
//监听endpoint的作用是通过funcname resolver出对应的函数调用address
endpointsInformer := kubeInformerFactory.Core().V1().Endpoints()
log.Println("Waiting for openfaas CRD cache sync")
//等待所有启动的 Informer 的缓存被同步
setup.profileInformerFactory.WaitForCacheSync(stopCh)
log.Println("Cache sync complete")
go faasInformerFactory.Start(stopCh)
go kubeInformerFactory.Start(stopCh)
go setup.profileInformerFactory.Start(stopCh)
controller模式很容易理解,说简单点就是启动一个httpServer,这个httpServer在启动的时候会加载众多crud逻辑的handler,
bootstrapHandlers := providertypes.FaaSHandlers{
FunctionProxy: proxy.NewHandlerFunc(config.FaaSConfig, functionLookup),
DeleteHandler: handlers.MakeDeleteHandler(config.DefaultFunctionNamespace, kubeClient),
DeployHandler: handlers.MakeDeployHandler(config.DefaultFunctionNamespace, factory),
FunctionReader: handlers.MakeFunctionReader(config.DefaultFunctionNamespace, kubeClient),
ReplicaReader: handlers.MakeReplicaReader(config.DefaultFunctionNamespace, kubeClient),
ReplicaUpdater: handlers.MakeReplicaUpdater(config.DefaultFunctionNamespace, kubeClient),
UpdateHandler: handlers.MakeUpdateHandler(config.DefaultFunctionNamespace, factory),
HealthHandler: handlers.MakeHealthHandler(),
InfoHandler: handlers.MakeInfoHandler(version.BuildVersion(), version.GitCommit),
SecretHandler: handlers.MakeSecretHandler(config.DefaultFunctionNamespace, kubeClient),
LogHandler: logs.NewLogHandlerFunc(k8s.NewLogRequestor(kubeClient, config.DefaultFunctionNamespace), config.FaaSConfig.WriteTimeout),
ListNamespaceHandler: handlers.MakeNamespacesLister(config.DefaultFunctionNamespace, kubeClient),
}
faasProvider.Serve(&bootstrapHandlers, &config.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:["+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)
查看函数部署接口的handler,MakeDeployHandler的实现,会看到它总共做了三件事:
- 构造Secrets资源,然后部署
- 构造deployment资源,然后部署
- 构造service资源,然后部署
其他操作类型的接口就不在这里展开。继续看另一种模式-operator。
operator这种模式使用了crd部署,openfaas支持把函数作为集群中的crd来创建,如下所示:
这是一个简单例子:
apiVersion: openfaas.com/v1
kind: Function
metadata:
name: nodeinfo
namespace: openfaas-fn
spec:
name: nodeinfo
image: functions/nodeinfo:latest
这是一个较为复杂的例子:
apiVersion: openfaas.com/v1
kind: Function
metadata:
name: nodeinfo
namespace: openfaas-fn
spec:
name: nodeinfo
handler: node main.js
image: functions/nodeinfo:latest
labels:
com.openfaas.scale.min: "2"
com.openfaas.scale.max: "15"
annotations:
current-time: Mon 6 Aug 23:42:00 BST 2018
next-time: Mon 6 Aug 23:42:00 BST 2019
environment:
write_debug: "true"
limits:
cpu: "200m"
memory: "256Mi"
requests:
cpu: "10m"
memory: "128Mi"
constraints:
- "cloud.google.com/gke-nodepool=default-pool"
secrets:
- nodeinfo-secret1
这种场景openfaas用了informer机制的另外一个重要特性,就是根据发生的事件类型,触发事先注册好的控制器回调。
首先创建监听,获取对deployment和function的共享索引信息的引用
deploymentInformer := kubeInformerFactory.Apps().V1().Deployments()
faasInformer := faasInformerFactory.Openfaas().V1().Functions()
然后创建事件广播,将指定类型添加到默认的Kubernetes Scheme中,这样就可以为faas-controller类型注册记录事件。
eventBroadcaster := record.NewBroadcaster()
...
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
然后添加openfaas crd的informer,同时设置处理程序。
faasInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.enqueueFunction,
UpdateFunc: func(old, new interface{}) {
controller.enqueueFunction(new)
},
})
//todo 为注册的类型设置事件处理程序,以及同步informer缓存和启动worker。