从零搭建 Node Faas(四)服务治理

服务治理一直是一个比较重要的环节,关系到线上服务的稳定性,以及可以及时发现问题并且能够快速定位问题。这篇文章主要梳理一下 Node Faas 的服务治理,主要涉及到监控、告警、日志的相关内容。

一、监控

1. 编译部署 Sentry 上报

在编译部署的过程中,需要将部署过程的错误日志上报到 Sentry。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Sentry.init({
//...
beforeSend: async (event, hint) => {
const error = hint.originalException
// 这里需要区分是否是 Promise 错误,以便决定是否使用 await 来执行
if (error instanceof Promise) {
await error.catch(async err => {
if (err instanceof CustomError) {
event.fingerprint = [...fingerprintArray, 'custom', err.type]
}
})
} else {
if (error instanceof CustomError) {
event.fingerprint = [...fingerprintArray, 'custom', error.type]
}
}
return event
},
})
Sentry.setTags({
// ...
})
Sentry.setExtras({
// ...
})

这里面有几个比较关键的注意点:

  1. beforeSend 函数中需要区分 Promise 错误,以便决定是否使用 await 来执行。
  2. fingerprint 用于区分错误类型,这里使用了自定义的错误类型。要注意特征值的设置,不然可能会出现同一个错误被 Sentry 认为是不同的错误。可能会让报警过于频繁,导致不必要的干扰。
  3. setTagssetExtras 用于设置错误的额外信息,这里设置了一些额外的信息,方便定位问题。

1.1 Sentry 进一步优化:错误类型归类

Sentry 的捕获是全局类型的捕获,他没有办法区分出来是哪个函数抛出的错误。所以如果需要在抛出错误的时候,将错误类型进行归类,有助于后面告警信息的快速定位。

比如原始的情况是下面这样的:

只是分为未捕获的 Promise 错误和未捕获的普通异常。这样没有额外的信息告诉我们这个错误是哪个函数抛出的。可能需要点进链接去定位。

如果做了区分,那就变成了下面这样:

这样就可以很快的定位到错误是哪个函数抛出的。

2. 使用 Prometheus 采集 Runtime 中的运行数据

对于用户来讲,需要提供一个监控的界面,让用户可以看到自己的函数的运行情况。这里可以使用 Prometheus 来采集数据。

监控分了三大类:

  1. HTTP 监控
    1.1 QPS
    1.2 请求耗时
    1.3 4xx、5xx 错误
  2. 运行情况监控
    2.1 app Crash 次数
    2.2 app 运行进程数量
  3. 子进程指标监控
    3.1 CPU 使用率
    3.2 内存使用率
    3.3 EventLoop 延迟
    // …

2.1 Prometheus 采集方案

这里值得一提的是,Faas 应用不同于普通的应用监控,因为 Faas 的应用是需要通过子进程来启动的,所以指标采集需要区分主进程和子进程。而且子进程采集到的数据,还要和主进程进行通信,以便主进程定期整合数据上报。

如上图所示,对于 HTTP 监控,可以直接在主进程中采集,当一个请求进来之后,会记录它的 count,耗时以及响应状态码等指标,然后通过 HTTP 接口暴露给 Prometheus。

对于运行情况监控同样也需要在主进程中采集,因为主进程控制了子进程的创建和分发以及监视其销毁。此外,主进程是稳定的,因此数据收集可靠且不会丢失。

对于子进程指标监控,需要在子进程中采集,当 Prometheus 从主进程收集数据的时候,会通过 UNIX socket 获取子进程的指标,主进程再整合数据上报给 Prometheus。

1
2
3
4
5
6
7
const promises = processDirList.map((dir) =>
axios.get('http://localhost/metrics', {
httpAgent: new http.Agent({
socketPath: path.resolve(dir, 'runtime.sock'),
}),
}),
)

如上代码,主进程通过 HTTP 请求子进程的 UNIX socket,获取子进程的指标数据。

2.2 Prometheus 采集数据展示

我们当然知道,Grafana 是一个非常好的数据展示工具,和 Prometheus 也可以很紧密的配合使用。但是由于 Grafana 的一些权限问题,没办法针对用户根据 app 进行数据隔离,所以需要自己实现一个数据展示的界面。

Prometheus API 提供了一些查询接口,可以通过这些接口获取到我们需要的数据,然后使用 echarts 来自己渲染图表。

当然你需要你写 PromQL 的知识储备,比如获取 QPS 的数据,你可能需要这样写:

1
sum by (path) (irate(http_request_seconds_count{app="${params.appId}"}[1m]))

二、日志

有两个部分需要收集日志,一部分是 Jenkins 的日志,还有一部分是 Server 的运行日志,当然执行 Function 也是需要日志记录的。我们分情况来说。

1. Jenkins 日志

Jenkins 的日志可以先配置一个 Blue Ocean 插件,然后也对外提供了一些 API,可以通过这些 API 来获取 Jenkins 的日志。比如可能有下面这样一个请求来获取日志。

1
this.axiosInstance.get<any, string>(`/pipelines/${name}/runs/${buildId}/log/`)

你的前端页面如果想有一个美观的展示,你可能需要 @jenkins-cd/pipeline-graph-widget 这个包,它可以帮你展示一个流程图,然后你可以点击节点来查看日志。

2. Server 日志

Server 的日志收集取决于你用的什么框架,这里以 NestJS 为例,可以使用 winston 来收集日志。下面是一个简化版本的 LoggerService。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
export class LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
consoleFormat(),
),
}),
new winston.transports.DailyRotateFile({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
filename: DEFAULT_LOG_FILE_NAME,
dirname: DEFAULT_LOG_DIR_NAME,
}),
],
});
}

log(message: string | IGeneralLogParams) {
this.logger.info(message);
}

error(message: string | IGeneralLogParams) {
this.logger.error(message);
}

warn(message: string | IGeneralLogParams) {
this.logger.warn(message);
}

debug(message: string | IGeneralLogParams) {
this.logger.debug(message);
}

verbose(message: string | IGeneralLogParams) {
this.logger.verbose(message);
}
}

核心就是日志格式的整理,以及将日志文件输出到指定位置即可。

3. 执行 Function 的日志

执行 Function 的日志和 Server 类似。因为 Function 运行在 Runtime 上,同样只需要把日志输出到文件即可。

不太一样的是,子进程中的 logger 需要和主进程的 logger 区分开比较好。因此需要在 vm 沙箱的 SandBox 中传递 logger。Sandbox 这部分逻辑你可以前往 Faas 运行时这篇文章进行查看。

三、告警

告警这部分内容和监控息息相关,Sentry 平台和 Grafana 都提供了告警的功能。我们可以通过这两个平台来设置告警规则。

然后你可以通过 Webhook 来将告警信息发送到你的 IM 工具,或者发送 email 等。

四、总结

那么到这里,Faas 服务治理相关内容就梳理完毕了,当然本篇文章整理的服务治理还是比较通用的,不仅仅是用在 Faas 中,其他的服务治理也是大同小异的,业界基本是有一套完整成熟的方案。