script language

从 2.0.1 升级到 5.1.1 有一阵子了,今天发现了一个存在比较久的问题,就是诸如 update 包括 bulk update 操作,不能被正常的执行。问题集中在那些在 body 中使用 script 的 query,而直接全文更新的则没有问题。例如,对 list 类型的 document 进行局部更新:

POST index/type/id/_update
{
"script": "ctx._source.tags -= tag",
"params" : {
"tag" : "blue"
}
}

这样的使用在 2.0.1 中是没问题的,然而在 5.x 中却报错:Variable [tag] is not defined. 无法执行这个 script,后来发现了 default lang 不再是 groovy 而变成了 painless ,而 painless 的取值需要携带 key ,即 params.tag 这样才可以正常找到值。那么 groovy 为什么不再是 default 还被新版本中标记为 deprecated 了呢……要知道 groovy 之前替换掉 mvel 的理由是足够快而且简单……

先说一下什么是 sandboxed language

沙盒是在受限的安全环境中运行应用程序的一种做法,这种做法是要限制授予应用程序的代码访问权限。

像 groovy 和 JavaScript 这类脚本语言它们本身都不是 sandboxed,它们可以做很多系统级别的不止是读写、网络请求的操作,这样就给基于 JAVA 并且在运行中默认开启 The Security Manager 的 elasticsearch 带来很大的安全隐患,比如在脚本中随便加一句 infinite loop ,服务器可能就表现成拒绝访问的状态,所以在之前的版本中 elasticsearch 为 groovy 加入了沙盒控制一些权限,然而后面由于权限限制不够,还是出现了一些问题23333。

虽然 5.x 仍然内置 groovy ,但是考虑到 elasticsearch script 的来源,可以是 inline、store 、还有 file,前两个就不说了,一个是 query 中直接写进去的像我上面的例子,另一个也是以数据的形式存在某个 cluster state 的 _script 节点下,而 file 的形式是配置在 elasticsearch 的 config 文件夹中,所以从安全的角度,elasticsearch 5.x 只对 file script 默认允许执行 groovy 。

至于开头的 query ,如果不考虑安全性,例如默认我们的 elasticsearch 运行与一个相对隔离的环境下,如果还想用 inline groovy ,就可以为 groovy 单独开启一个配置 script.engine.groovy.inline: true 或者更宽泛的,针对所有 inline script 的配置script.inline: true,那么为上面的 script 声明一下 lang 就可以成功执行了(在 Python 和 Node.js 包中拼 dict 和 object 也是一样):

POST index/type/id/_update
{
"script" :{
"inline": "ctx._source.tags -= tag",
"lang": "groovy",
"params" : {
"tag" : "blue"
}
}
}

等 painless 相对稳定了,直接切换过去就可以了,毕竟语法都类似,而且还安全。

upsert element to exist document

一个已经存在的 document 可能有一个 tags 的 element ,它是一个 Array 形态,现在我们想 upsert 某个 tag 进去。

这种 array 的操作通常是用 script 操作的,于是很直观地用到文档中的 upsert:

{
"script": {
"inline": "ctx._source.tags += tag",
"lang": "groovy",
"params": {
"tag": "皮皮虾"
}
},
"upsert": {
"tags": ["皮皮虾"]
}
}

然而 script 总是执行而 upsert 不执行,原因是 document 已经存在了,这个 upsert 只是针对当 document 不存在时,所以还是要把逻辑做在 script 中:

{
"script": {
"inline": "if (ctx._source.tags) {ctx._source.tags += tag;} else {ctx._source.tags = [tag]}",
"lang": "groovy",
"params": {
"tag": "皮皮虾"
}
}
}