函数计算-处理容器中的网络访问日志

处理容器日志

我的 web 应用是使用 docker 进行部署的,容器的日志由 fluentd 收集到阿里云的 SLS,但容器收集到的日志是这样的:

函数计算-处理容器中的网络访问日志

可以看到,日志的真正内容都放在了 log 字段里,不是很便于索引和查看,但有了函数计算,我们就可以把这些日志读出来,然后把 log 字段提取并处理好,然后写入新的 log store 即可。原理见下图(也可以参考阿里云官方文档):

函数计算-处理容器中的网络访问日志

首先我们需要在函数计算控制台里面,建立一个函数计算的服务,注意需要和 SLS log store 在同一个地域:

函数计算-处理容器中的网络访问日志

建议在这里直接配置高级配置,设置对应的 RAM 角色,可以在这里直接新建角色,比较方便。为了简单,我们这里的策略模板,授予该角色日志的所有访问权限,待会儿我们可以在 RAM 控制台重新修改这个权限,对这个角色做更细粒度的权限控制。
创建好服务之后,我们就可以新建函数了,函数计算提供了很多的模板,这里我们采用 Node.js 完成这个新的函数,选择空白函数:

函数计算-处理容器中的网络访问日志

触发器配置中,因为我们需要从日志 store 1 读取日志进行处理,因此触发器选择日志服务:

函数计算-处理容器中的网络访问日志

接下来我们需要对触发器进行配置:

函数计算-处理容器中的网络访问日志

LogStore 相当于是日志源,就是最开始架构图中的 LogStore1,触发器日志就是用于存放函数计算过程中产生的日志的地方,方便排查代码问题。

函数配置是非常有用的配置了,这段配置可以在代码中获取到,类似于向 docker container 里面传递环境变量一样,可以让函数更具备扩展性。这里我们通过函数配置,配置了两个目标 LogStore,一个用于存放分析出来的 NGINX access log:access-log,另外一个 error-log,存放 docker 日志分析出来的其它内容,比如 PHP error、NGINX error 等其它日志。

点击下一步之后,我们就可以创建函数内容了:

函数计算-处理容器中的网络访问日志

这里,经过我多个小时的调试,完成了这个函数,具体过程暂且不表,函数内容我已经上传到 GitHub:地址,待会儿再跟大家分享编写函数代码过程中的一些问题,这里,我们可以先使用默认提供的函数完成设置步骤。

函数计算-处理容器中的网络访问日志

最下面还有一些函数运行时的设置,基本都不用改,函数内存和超时时间可以根据自己的需要适当扩大一些,避免运行过程中遇到错误。也可以这里设置小一些,后面根据函数运行的状况,不够用的话再调大。

最后一步是信息核对,如果没有问题,我们的函数就创建好了,接下来就是激动人心的函数编写过程了。

我们的需求是从 LogStore1 读取日志,进行分析之后,存入 LogStore2,所以我们先看如何从 LogStore1 读取数据。
对于这个函数,有个入口函数:

module.exports.handler = function(event, context, callback) {
  var config = JSON.parse(event.toString());
  console.log(config);
  console.log(context);
  callback(null, 'done');
}

可以看到我们能默认拿到 event, context 两个参数,这两个参数我们可以通过 console.log 打印一下看看是什么(见上面的代码),但注意 event 是个 Buffer,所以我们需要先处理一下。处理之后,我们看一下 event 的内容:

{
  "parameter": {
    "source": {
      "endpoint": "https://cn-hangzhou-intranet.log.aliyuncs.com"
    },
    "target": {
      "endpoint": "https://cn-hangzhou-intranet.log.aliyuncs.com",
      "errorLogStore": "error-log",
      "logStore": "access-log",
      "project": "test"
    }
  },
  "source": {
    "endpoint": "https://cn-hangzhou-intranet.log.aliyuncs.com",
    "projectName": "test",
    "logstoreName": "nginx-access",
    "shardId": 0,
    "beginCursor": "xxxx",
    "endCursor": "xxxx"
  },
  "jobName": "xxxx",
  "taskId": "xxxx",
  "cursorTime": 11111111
}

可以看出来,我们之前配置的“函数配置”是放在 event.parameter 里面的,event.source 则包含了 Log 触发器传递过来的一些信息,比如当前触发任务下的日志 beginCursor 和 endCursor,所以我们需要使用阿里云 OpenAPI SDK 从 SLS 中查到 beginCursor -> endCursor 中的日志。

context 参数里面,则包含了一个 credentials,里面是我们能够访问其他云产品的临时 AK,有效期 5分钟,这样我们就不用把自己再单独生成一个 AK 了。

但翻阅了 SLS 的 API 文档,发现 PullLogs 接口 只接受 beginCursor 和 count,好在 Response Header 里面给出了 nextCursor:x-log-cursor。我们可以依据这个,每次取一条,直到取到 x-log-cursor 和 endCursor 想等的情况下结束(具体需不需要包含 endCursor 我也不是很清楚,文档里貌似没看到,估计要看下其他语言的函数模板里面的代码)

所以我们需要递归去把这些日志捞出来。如果单纯用 callback 方式,写起来比较蛋疼,翻阅文档之后,发现函数计算中的 Node 脚本,默认注入了一些模块,比如 tj 大神的 co,aliyun-sdk 等,就可以不用自己 npm install 后再上传上来了。具体可以参考代码中的 pullLogs 函数。

日志查询出来之后,我们从日志信息中取出 log 字段,使用正则把需要的数据匹配出来,见代码中的 processNginxLogs 函数。

处理完成之后,我们再写入 LogStore2

代码本身比较简单,我就不深入介绍具体的过程了,可以参考 GitHub 上的源码,代码写的比较渣,欢迎各位大神提 PR。这里分享下编写函数过程中的一些心得:

  1. 调试起来只能靠 console.log,建议代码里面多打一些 console.log,方便排查问题,在线编写之后,保存并执行,下面就会有“执行日志”:
    函数计算-处理容器中的网络访问日志

但这个日志只能显示一部分,如果你的日志比较多的话,这里是显示不全的,也没法翻页或者滚动,具体的还需要到 SLS 控制台,找到我们配置的函数计算的日志仓库,看日志。

  1. 函数的执行内存,可以在测试执行的时候看到执行的内存和执行时间,见上一条里面的第一张图,大家可以根据这个设置你的函数需要的内存和超时时间,设置过低的话,函数执行就很容易出现错误,会出现“process exited unexpected……”之类的错误,如果日志里频繁出现这种错误的话,可以看看是不是这里的问题。
  2. 很多时候,自己测试执行的时候没有问题,但是一旦由触发器执行就一堆报错了,这个时候,建议大家去看 SLS 里的函数计算日志,找到函数计算传入的函数配置:
    函数计算-处理容器中的网络访问日志

然后把 task_config 里面的字段复制到函数执行,配置测试事件的自定义事件里面,再点执行,就可以看到该条的执行记录:
函数计算-处理容器中的网络访问日志

  1. 过多的日志也会造成写入到 SLS 里的日志太多太乱,解决方法也比较简单,在函数配置里面加一个开关,比如 debug,然后在代码中判断 debug 是否开启,开启了再打日志,不然就不打,这样 SLS 里面的 Log 就会少很多,方便找内容,也给自己省点资源:)

基本上,经过这些步骤,我们的日志消费函数就完成了。等一段时间,可以看到已经有一些解析好的日志输出了:

函数计算-处理容器中的网络访问日志

可以看到各个字段已经被我们拆出来了,就可以放到 MaxCompute 等产品里面进行分析了。

进阶:解析日志并包装接口

正在沾沾自喜的时候,老板来了:每次查看日志还是得到控制台里面查看,比较麻烦,而且里面都是各种 ip 地址,看起来都不知道来自哪里。老板不满意了,为了不让年终奖飞了,所以我们需要做以下事情:

  1. 新建一个函数,从处理后的 log store 查找出来某个时间段内的所有 NGINX 日志
  2. 调用 API 云市场里面的免费 API,把 ip 解析为地理位置
  3. 把这些日志包装成一个 API 出来,这样就可以写一个简单的网页调用这个 API,把日志展示出来,甚至后面还可以基于开源图表库画个酷炫的图表~

总共需要连接以下云产品:SLS、API Gateway、API 云市场

本例中我们继续采用 Node.js,但考虑到可能需要采用非自带的一些 npm 包,所以我们这次体验一下基于 fcli 来创建函数。

首先到帮助文档中下载 fcli 然后参照文档配置 AK:文档地址

然后新建一个代码目录,也可以直接 clone 我写好的代码:GitHub地址

在该目录下执行 fcli shell,这里我们就偷懒下,直接在已有的服务里面新建一个函数,并且配置 runtime 为 nodejs6,配置代码地址等(文档地址):

Welcome to the function compute world. Have fun!
>>> ls // 先看看我们已有的服务
test-log-filter
>>> cd test-log-filter // 进入该服务
>>> ls // 查看该服务下有哪些已有的函数
log-processor
test-whether
>>> mkf nginx-log-api -d ./ -h index.handler -t nodejs6 // 创建相应的函数
>>> ls // 再看一下已经创建成功了
log-processor
nginx-log-api
test-whether

接下来我们需要通过 OpenAPI-SDK 获取 SLS 里面的日志,有了之前处理 log 的经验,这个过程就很简单了,参考代码中 lib/sls.js 里面的部分。

获取到之后,接下来我们需要拿每个 log 中的 ip 字段去做地理位置解析,我们到云市场里购买一个 IP 查询的服务:地址。购买之后,调用也很简单,使用 aliyun-api-gateway 这个 npm 包完成请求过程

最后,需要按照 API Gateway 要求的返回格式进行返回,参考文档中 3.2 这个部分
写代码的过程中肯定少不了修改和调试的过程,可以通过 fcli 这条命令把代码上传/更新到函数计算服务中:

>>> upf functionName -d ./ -h index.handler -t nodejs6

更新完之后,我们就可以在控制台里面,配置测试事件进行测试了,输入的内容格式参考文档中 3.1 这个部分。

代码写好之后,我们就可以对接 API Gateway 了。在 API Gateway 控制台里面新建一个 API:

函数计算-处理容器中的网络访问日志

这个步骤根据自己的需要进行填写。接下来需要定义请求参数等,我们需要 from 和 to 两个 query,表示日志的起止时间

函数计算-处理容器中的网络访问日志

下一步,我们定义后端类型,这里选择函数计算,同时需要获取 ram 授权,点击获取授权后会跳转到 ram 控制台获取授权,成功之后,我们还需要在 ram 控制台里面找到该角色(FC关键字),找到之后,点击这个角色的详情,把里面的 arn 这个字段复制到后端基础定义这里,稍微麻烦一点,其它根据自己的需要进行填写,函数需要填写我们刚才创建的函数,不然调用不到。

函数计算-处理容器中的网络访问日志

下面我们可以定义一些常量参数,这里非常有用,也推荐大家使用这个功能,这里配置的参数可以根据你配置的规则传入到函数里面,非常适合放置一些配置内容以及一些敏感的信息,比如你的 SLS logStore,云市场 API 请求的 AppCode 等。

函数计算-处理容器中的网络访问日志

比如我这里配置的是在 query 变量获取,不过我建议可以跟上面的后端参数分开会更好一些
最后一步定义返回格式和错误码,可以根据自己需要配置下,这样 API 就配置好了

配置好之后,我们就可以使用 API Gateway 提供的调试功能进行调试啦:

函数计算-处理容器中的网络访问日志

填写 from 和 to 两个字段,点击发送请求,就可以看到我们的 API 返回结果了。可以看到,我们正确的查出了 ip 对应的地理位置信息,放到了 location 字段。部分查询不到的,有些字段会返回空。

至此,我们的 API 就创建好了,可以在自己的业务中基于这个 API 查询日志,甚至可以画个酷炫的图表什么的,接下来就看你的啦~

上一篇:GDPR生效首日:涉欧盟业务企业的数据合规启示


下一篇:《计算机系统:系统架构与操作系统的高度集成》——1.3 操作系统的作用