# 4.3 faas-nomad源码分析

`faas-nomad`的分析按初始化、工作机制两部分。

### 初始化

初始化的过程要和faas-provider SDK的交互，联着分析。

* 组件依赖
* 身份认证
* faas-provider定义的逻辑
* 具体Provider相关Handlers创建

faas-nomad通过`makeDependencies`将需要的nomad、consul、statsd三个外部组件依赖的API客户端创建出来。

```go
makeDependencies(
		*statsdServer,
		*nodeURI,
		*nomadConfig,
		*consulAddr,
		*consulACL,
		*nomadRegion,
)
```

调用faas-provider定义的初始化逻辑，将

```go
bootstrap "github.com/openfaas/faas-provider"
...
bootstrap.Serve(handlers, config)
```

faas-provider的`Serve`将路由与处理请求的Handlers绑定。faas-provider中规定好了这些`FaaSHandlers`，这些Handlers在OpenFaaS API Gateway路由处也有设置，由网关转发给具体的Provider处理。

```go
// FaaSHandlers provide handlers for OpenFaaS
type FaaSHandlers struct {
	FunctionReader http.HandlerFunc
	DeployHandler  http.HandlerFunc
	// FunctionProxy provides the function invocation proxy logic.  Use proxy.NewHandlerFunc to
	// use the standard OpenFaaS proxy implementation or provide completely custom proxy logic.
	FunctionProxy  http.HandlerFunc
	DeleteHandler  http.HandlerFunc
	ReplicaReader  http.HandlerFunc
	ReplicaUpdater http.HandlerFunc
	SecretHandler  http.HandlerFunc
	// LogHandler provides streaming json logs of functions
	LogHandler http.HandlerFunc

	// Optional: Update an existing function
	UpdateHandler http.HandlerFunc
	HealthHandler http.HandlerFunc
	InfoHandler   http.HandlerFunc
}
```

基于前述依赖的组件，开始`createFaaSHandlers`

```go
handlers := createFaaSHandlers(nomadClient, consulResolver, stats, logger)

//createFaaSHandlers：
...
return &types.FaaSHandlers{
		FunctionReader: handlers.MakeReader(nomadClient.Jobs(), logger, stats),
		DeployHandler:  handlers.MakeDeploy(nomadClient.Jobs(), *providerConfig, logger, stats),
		DeleteHandler:  handlers.MakeDelete(consulResolver, nomadClient.Jobs(), logger, stats),
		ReplicaReader:  makeReplicationReader(nomadClient.Jobs(), logger, stats),
		ReplicaUpdater: makeReplicationUpdater(nomadClient.Jobs(), logger, stats),
		FunctionProxy:  makeFunctionProxyHandler(consulResolver, logger, stats, *functionTimeout),
		UpdateHandler:  handlers.MakeDeploy(nomadClient.Jobs(), *providerConfig, logger, stats),
		InfoHandler:    handlers.MakeInfo(logger, stats, version),
		Health:         handlers.MakeHealthHandler(),
		SecretHandler:  handlers.MakeSecretHandler(vs, logger.Named("secrets_handler")),
	}
```

实例化这些Handlers，在`handlers`包下。

这里面需要注意的是`makeFunctionProxyHandler`。它创建了一个处理函数的中间件，这个中间件感觉就像切面的概念，在下一个`next http.HandlerFunc`处理前验证了请求参数中函数名是否存在。

```go
// MakeExtractFunctionMiddleWare returns a middleware handler which validates
// the presence of the function name in the URI query.
func MakeExtractFunctionMiddleWare(
	getVars func(*http.Request) map[string]string,
	next http.HandlerFunc) http.HandlerFunc {

	return func(rw http.ResponseWriter, r *http.Request) {
		vars := getVars(r)
		functionName := vars["name"]

		if functionName == "" {
			rw.WriteHeader(http.StatusBadRequest)
			fmt.Fprint(rw, fmt.Errorf("No function name"))
			return
		}

		r = r.WithContext(context.WithValue(r.Context(), FunctionNameCTXKey, functionName))
		next(rw, r)
	}
}
```

handlers包中的`Proxy`是一个`http.Handler`，是真正用于调用下游函数的方法。具体来说，对faas-nomad Provider，下游函数是Nomad的job方式组织运行的，而且当前特定的是打包成docker镜像（其中有watchdog，来启动、监控forked process），以nomad的docker驱动启动job。

### 工作机制

上面说到，faas-nomad的main.go `main`方法最后调用了faas-provider的`Serve`方法，这是一个阻塞方法，默认在`8080`端口，启动了server，接受Gateway路由到Provider的调用请求，具体地，处理这些请求：CRUD函数、调用函数、设置副本数……。

faas-provider的功能在于调用nomad、consul、vault、statsd等客户端API，在调用过程中，有些缓存优化，如缓存函数名等。

以delete函数为例，Handler定义：

```go
// main.go:150
DeleteHandler:  handlers.MakeDelete(consulResolver, nomadClient.Jobs(), logger, stats),
```

调用nomad client的job API，删除函数，再删除函数名的缓存

```go
// handlers/delete.go:41
// Delete job /v1/jobs
_, _, err = client.Deregister(nomad.JobPrefix+req.FunctionName, false, nil)
// client 经过本地的封装 定义是 client nomad.Job
// func MakeDelete(sr consul.ServiceResolver, client nomad.Job, logger hclog.Logger, stats metrics.StatsD) http.HandlerFunc 

...
// handlers/delete.go:51
sr.RemoveCacheItem(req.FunctionName)
```

正是`MakeDelete`方法传入的，实例化只赋值了`Register`、`Info`、`List`、`Deregister`、`Allocations`需要的方法。

```go
// main.go:150
nomadClient.Jobs()  -> client nomad.Job
```

nomad.Job 中 Job接口定义的方法与完全相同，只是抽出来faas-nomad中需要的接口，屏蔽了真实的nomad `jobs` API的细节。这是Go语法的特点，刚看到的时候有点奇怪（我没有完整地系统学GO语法），要是Java的话得声明相同的接口吧，其中不用的方法，还占用内存；这种赋值，也是Go语言中“实现接口的结构体中所有方法，即是接口定义的类型”的运用吧。

```go
type Job interface {
	// Register creates a new Nomad job
	Register(*api.Job, *api.WriteOptions) (*api.JobRegisterResponse, *api.WriteMeta, error)
	Info(jobID string, q *api.QueryOptions) (*api.Job, *api.QueryMeta, error)
	List(q *api.QueryOptions) ([]*api.JobListStub, *api.QueryMeta, error)
	Deregister(jobID string, purge bool, q *api.WriteOptions) (string, *api.WriteMeta, error)
	Allocations(jobID string, allAllocs bool, q *api.QueryOptions) ([]*api.AllocationListStub, *api.QueryMeta, error)
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jahentao.gitbook.io/openfass/4.3-faasnomad-yuan-ma-fen-xi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
