OpenFaaS架构——faas-netes

1,864 阅读3分钟

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机制的另外一个重要特性,就是根据发生的事件类型,触发事先注册好的控制器回调。

首先创建监听,获取对deploymentfunction的共享索引信息的引用

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。