插件的作用和分类
elasticsearch
(简称es)是一款运用广泛的支持实时搜索的数据库。常用于全文检索、时序数据分析等领域。es本身基于模块设计,同时支持插件定制,当现有的功能和插件无法满足需求的时候,我们可以基于es的插件框架和接口,实现自己的插件,完成功能。
常见的插件,有中文分词、hdfs数据备份还原、云平台自动发现(ec2, gce, azure)等。es的收费模块xpack
也是基于插件机制开发出来的,xpack提供了诸如index声明周期管理、es监控、安全认证、告警、机器学习、sql等实用功能。
使用插件实现这些功能的好处是,能够更好的整合es的基础设施,方便部署。
es的插件主要分为如下几大类:
- 分词插件
- 扩展restful接口
- 集群和自动发现
- Ingest插件
- 存储插件
- 脚本扩展
- 搜索相关
- 一般插件
这个系列将会基于笔者的实际开发经验,介绍其中扩展restful接口
, Ingest插件
和一般插件
。
es提供的基础设施
在开始开发前,我们可以先了解一下,作为es的插件可以从es的中获取哪些可直接使用的基础接口。
- Client: 相当于restful Client,但其内部实现并不是简单的http请求
- ClusterService: 集群相关管理接口
- ScriptService: script相关
- ThreadPool: es管理的线程池,完成多线程或定时任务
- NodeEnvironment: 节点环境
- ...
实时上基础设施还有许多,笔者也不是全部了解。只是罗列一下用到过的。es官方对插件开发的文档支持还是很有限的,所以如果希望进行插件开发的话,学习已有的插件源码,甚至是跟踪到es核心源码几乎是必须的。
插件部署
插件在es中运行,除了需要实现一些接口,还需要遵循一些约定。这些约定关系到开发和部署。
在es中外部插件被部署在plugins
目录下。有两种方式可以部署插件,一种是通过es自带的elasticsearch-plugin
命令安装打包好的插件zip包;另一种是直接复制插件到plugins目录下。一般在开发测试阶段使用第二种方式比较方便。如果希望别人使用,则最好按照要求发布成zip包。
但无论是哪种方式,一个插件最终能被es加载必须满足几个条件:
- 在
plugins
目录下有对应插件的目录 - 目录中包含打好的jar包和依赖包
- 目录中包含
plugin-descriptor.properties
插件描述文件 - 目录中包含
plugin-security.policy
文件,声明插件会用到的java安全项
以repository-hdfs
插件为例,插件中的包含文件如下(省略了依赖的jar包):
repository-hdfs
├── plugin-descriptor.properties
├── plugin-security.policy
├── repository-hdfs-7.0.0.jar
...
plugin-descriptor.properties
例如
description=The HDFS repository plugin adds support for Hadoop Distributed File-System (HDFS) repositories.
version=7.0.0
name=repository-hdfs
classname=org.elasticsearch.repositories.hdfs.HdfsPlugin
java.version=1.8
elasticsearch.version=7.0.0
extended.plugins=
has.native.controller=false
plugin-descriptor.properties
中最关键的部分是elasticsearch.version
和classname
。前者声明了插件兼容的es版本(这个版本必须与正在运行的es版本exactly一致),后者是插件的入口类
plugin-security.policy
例如
grant {
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.util.PropertyPermission "*", "read,write";
permission java.lang.RuntimePermission "shutdownHooks";
permission javax.security.auth.AuthPermission "getSubject";
permission javax.security.auth.AuthPermission "doAs";
permission javax.security.auth.PrivateCredentialPermission "org.apache.hadoop.security.Credentials * \"*\"", "read";
permission java.lang.RuntimePermission "accessClassInPackage.sun.security.krb5";
permission javax.security.auth.AuthPermission "modifyPrivateCredentials";
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KeyTab * \"*\"", "read";
permission javax.security.auth.PrivateCredentialPermission "javax.security.auth.kerberos.KerberosTicket * \"*\"", "read";
permission java.lang.RuntimePermission "loadLibrary.jaas";
permission java.lang.RuntimePermission "loadLibrary.jaas_unix";
permission java.lang.RuntimePermission "loadLibrary.jaas_nt";
permission javax.security.auth.AuthPermission "modifyPublicCredentials";
permission java.security.SecurityPermission "putProviderProperty.SaslPlainServer";
permission java.security.SecurityPermission "insertProvider.SaslPlainServer";
permission javax.security.auth.kerberos.ServicePermission "*", "initiate";
permission java.net.SocketPermission "*", "connect";
permission java.net.SocketPermission "localhost:0", "listen,resolve";
};
plugin-security.policy
是插件对敏感资源(比如本地文件)或者应用代码(类的方法)的访问声明。由于es是在java.security
开启的情况下运行插件的,所以有很多我们悉数平常的操作都无法通过jvm的Access Control机制的检查(例如读写文件、反射、Classloader操作、Socket相关),会报莫名其妙的权限问题。es要求插件开发者在plugin-security.policy
中声明对敏感资源的需求,并在代码中使用AccessController.doPrivileged
包裹包含敏感操作的代码。
插件开发的难点
笔者总结了在插件开发过程中遇到的难点:
- jar hell。es对有冲突版本的jar包采取了绝不姑息的态度。无论是es自身依赖的lib,还是插件的lib,插件之间的lib,但凡在加载过程过发现有jar包冲突,就会报
jar hell
的错误。这给插件管理依赖带来了很大的困难。即使你将es自身的依赖排除在外,也无法保证系统安装的其他插件的依赖jar包与你插件的依赖jar包没有冲突。 - 调试困难。虽然es提供了专门用于单元测试的测试框架,但这块几乎没有什么文档,只能自己通过查看官方的插件实现来慢慢摸索。关于具体开发也没有明确的指导,基本只能靠摸索。
- 版本强一致。上面也提到了,
plugin-descriptor.properties
的中的elasticsearch.version
必须与运行的es版本完全一致。这给插件的版本分发带来了困难。 - security.policy问题。
security.policy
其实很多开发者不熟悉的,往往会出现插件跑起来各种报权限不够。所以需要在调试阶段逐个去尝试,尤其是你如果依赖其他jar,你可能无法控制这些jar的内部行为,从而无法用AccessController.doPrivileged
包裹其内部线程需要访问敏感资源的代码。有的时候,你发现你已经声明了plugin-security.policy
,也用AccessController.doPrivileged
做了你觉得正确的处理,还是出现权限不够,那么就要考虑是不是你依赖的jar包的内部线程有访问敏感资源。