Ambari 插件开发笔记

Ambari插件开发

Apache Ambari项目旨在通过开发用于配置,管理和监控Apache Hadoop集群的软件,使Hadoop管理更简单。Ambari提供了一个直观的,易于使用的Hadoop管理Web UI,由其RESTful API支持。但有时候有一些自定义非官方的需求,这时就需要基于ambari做二次开发了。

Ambari是支持服务扩展的,只需要,编写自定义服务相关配置、脚本,然后扔到它的资源目录,重启服务器就可以了。听起来很简单。它的服务组织结构是分Stack / Service /Component 3层的 。

开发的一个java 服务插件定义目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[daxiang@ark1 common-services]$ pwd
/var/lib/ambari-server/resources/common-services
[daxiang@ark1 common-services]$ tree ARK_DATA_API/
ARK_DATA_API/
└── 0.0.1
├── alerts.json
├── configuration
│   ├── ark-data-api-env.xml
│   └── ark-data-api-server.xml
├── metainfo.xml
└── package
├── archive.zip
├── scripts
│   ├── ark_app_api.py
│   ├── params.py
│   └── status_params.py
└── templates
└── application.properties.j2

5 directories, 9 files

其中 ARK_DATA_API 为服务名

  • metainfo.xml:服务定义描述文件
  • alerts.json: 定义了告警的配置
  • configuration:目录配置了服务允许所需要的环境参数 和 运行参数,这些参数可以通过ambari界面进行管理和修改。
  • package:包括scripts 和 templates,scripts 定义了从安装、配置、启动、停止和状态的脚本,我这里是python。template 程序运行的配置文件,通过ambari 界面管理的参数 通过这个模版进行替换,生成程序的最终配置,供程序运行使用

metainfo.xml 配置说明(我的服务比较简单,就借用参考文章的做下记录)

ambari-server/resources/common-services/{A_SERVICE}/metainfo.xml
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0"?>
<metainfo>
<schemaVersion>2.0</schemaVersion>
<services>
<service>
<name>ELASTICSEARCH</name>
<displayName>ElasticSearch</displayName>
<comment>ElasticSearch service</comment>
<version>1.4.0</version>
<components>
<component>
<name>ELASTICSEARCH</name>
<displayName>ElasticSearch</displayName>
<category>MASTER</category>
<cardinality>1</cardinality>
<commandScript>
<script>scripts/master.py</script>
<scriptType>PYTHON</scriptType>
<timeout>600</timeout>
</commandScript>
</component>
<component>
<name>ELASTICSEARCH_NODE</name>
<displayName>ElasticSearchNode</displayName>
<category>SLAVE</category>
<cardinality>ALL</cardinality>
<auto-deploy> <!--自动部署,仅在cardinality满足的情况下,界面上就不问,在这台主机,是否安装这个组件了-->
<enabled>false</enabled>
</auto-deploy>
<commandScript>
<script>scripts/slave.py</script>
<scriptType>PYTHON</scriptType>
<timeout>600</timeout>
</commandScript>
<!--<configFiles>
<configFile>
<type>env</type>
<fileName>elasticsearch-env.xml</fileName>
<dictionaryName>elasticsearch-env</dictionaryName>
</configFile>
</configFiles>-->
</component>
</components>
<osSpecifics>
<osSpecific>
<osFamily>any</osFamily>
<packages>
<package>
<name>elasticsearch</name>
</package>
</packages>
</osSpecific>
</osSpecifics>
<requiredServices>
<service>GANGLIA</service> <!--依赖服务,安装的时候限制,没有不能继续安装-->
</requiredServices>
<configuration-dependencies>
<config-type>elasticsearch-env</config-type>
</configuration-dependencies>
<monitoringService>false</monitoringService> <!--如果是监控服务的话,每个组件重启,他都要重启-->
</service>
</services>
</metainfo>
  1. 服务和组件名字一定要大写,不然后面你会全部重新来过的。不要问我为什么知道,我重试了N次。
  2. package节点的name 很重要。yum install [name]
  3. category节点组件类型可以有MASTER,SLAVE,CLIENT几种,那p2p集群咋办?
  4. MASTER不是必须得有,只有SLAVE也可以
  5. cardinality节点是安装基数。1+ 最少一个 , 0-1 最多一个 ,ALL 全部

一个基础的主配置脚本如下:

master.py

master.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
from resource_management import *
class Master(Script):
def install(self, env):
print 'Install the ES Master';
def stop(self, env):
print 'Stop the ES Master';
def start(self, env):
print 'Start the ES Master';

def status(self, env):
print 'Status of the ES Master';
def configure(self, env):
print 'Configure the ES Master';
if __name__ == "__main__":
Master().execute()

举例(一个自定义jar 服务)

OK,现在贴上我的配置。单jar包,已经打成rpm包,运行jar包时使用了ambari生成的配置文件,对项目中的配置文件进行覆盖。

1
2
#$conf_file 为通过ambari 模版生成的application.properties文件
java -jar $jar_file --spring.config.location=classpath:/,$conf_file

服务描述文件:metainfo.xml:

我这metainfo.xml里面的关键信息包括(信息还是非常少的,少了依赖、集群等等等):
名词和显示名称;
只安装到一台机器;
脚本为scripts/ark_app_api.py
yum安装包名ark-data-api*

metainfo.xml
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
<?xml version="1.0"?>
<metainfo>
<schemaVersion>2.0</schemaVersion>
<services>
<service>
<name>ARK_DATA_API</name>
<displayName>Ark data_api</displayName>
<comment>方舟data-api</comment>
<version>0.0.1</version>
<components>
<component>
<name>ARK_DATA_API</name>
<displayName>Ark data_api</displayName>
<category>MASTER</category>
<cardinality>1</cardinality>
<timelineAppid>arkb</timelineAppid>
<commandScript>
<script>scripts/ark_app_api.py</script>
<scriptType>PYTHON</scriptType>
<timeout>600</timeout>
</commandScript>
</component>
</components>

<osSpecifics>
<osSpecific>
<osFamily>any</osFamily>
<packages>
<package>
<name>ark-data-api*</name>
<skipUpgrade>true</skipUpgrade>
</package>
</packages>
</osSpecific>
</osSpecifics>
</service>
</services>
</metainfo>

其中scripts/ark_app_api.py:也是5个方法,分别是install 、configure、start、stop、status。

  • install:通过ambari安装 yum install ark-data-api*
  • configure: 安装完成后,做的一些配置信息,比如根据模版信息生成相关文件、生成相关的文件夹,改变用户组和权限等等
  • start: 如何启动服务。我这是先检测服务允许状态,再通过脚本启动(这个脚本我是打包到rpm包里面的,安装时就放到相关的文件夹中了。而且脚本有维护pid操作)
  • stop: 如何关停服务。我这也是脚本
  • status: 通过检测pid文件查询服务状态。(上面的服务启停脚本维护了pid文件)

插件主脚本:ark_app_api.py 参考

package/scripts/ark_app_api.py
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# coding=utf8
import random
import sys
from resource_management import *
from resource_management.libraries.script.script import Script
from resource_management.libraries.functions import get_unique_id_and_date
from resource_management.libraries.functions import conf_select
from resource_management.libraries.functions import stack_select
from resource_management.libraries.functions import StackFeature
from resource_management.libraries.functions.version import compare_versions, format_stack_version
from resource_management.libraries.functions.stack_features import check_stack_feature
from resource_management.libraries.functions.security_commons import build_expectations, \
cached_kinit_executor, get_params_from_filesystem, validate_security_config_properties, \
FILE_TYPE_JAAS_CONF
from resource_management import *
from resource_management.core.logger import Logger
from resource_management.core.resources.system import Execute
from resource_management.libraries.functions.check_process_status import check_process_status
from resource_management.libraries.functions.format import format
from resource_management.libraries.functions.validate import call_and_match_output
from pickle import STOP
from time import sleep
import ambari_simplejson as json

class Master(Script):

def install(self, env):
import params
self.install_packages(env)
Execute("chown -R {0}:{1} {2}".format(params.app_user,params.app_group,params.install_dir))


def configure(self, env):
# params 见下一个文件
import params
env.set_params(params)

Directory(params.app_conf_dir,
mode=0755,
owner=params.app_user,
group=params.app_group,
create_parents = True
)

# 这里根据Templates目录下的.j2文件生成最终程序所需的配置文件
File(params.app_conf_dir + '/application.properties',
content=Template('application.properties.j2'),
owner=params.app_user,
group=params.app_group,
mode=0755
)

#init system dir
Directory(params.install_dir,
mode=0755,
owner=params.app_user,
group=params.app_group,
create_parents = True
)
# 省略其他权限、文件夹等配置

def start(self ,env):
import params
env.set_params(params)
self.configure(env)

no_op_test = format("ls {app_pid_path} >/dev/null 2>&1 && ps `cat {app_pid_path}` | grep `cat {app_pid_path}` >/dev/null 2>&1")

#startup.sh 是打在rpm包中的
Execute("sh {0}/startup.sh".format(params.app_bin_dir),
user=params.app_user,
not_if=no_op_test
)

def stop(self ,env):
import params
env.set_params(params)
self.configure(env)

no_op_test = ""

#shutdown.sh 是打在rpm包中的
Execute("sh {0}/shutdown.sh".format(params.app_bin_dir),
user=params.app_user
)

def status(self, env):
import status_params
env.set_params(status_params)
#pid文件 是启停脚本维护的
check_process_status(params.app_pid_path)

if __name__ == "__main__":
Master().execute()

其中脚本中用到的 params.app_bin_dirstatus_params.app_pid_path 参数,都是来自同目录下定义的参数配置文件 params.py。status_params 就定义了一个pid 文件的路径,就不贴了。

参数定义:params.py

也可以和 主脚本写在一起,不过参数多了还是分开写方便

params.py
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

#!/usr/bin/env python
from resource_management.core.logger import Logger
from resource_management.libraries.functions import conf_select
from resource_management.libraries.functions import stack_select
from resource_management import *
from resource_management.libraries.functions.get_not_managed_resources import get_not_managed_resources
from resource_management.libraries.functions.expect import expect
from ambari_commons.ambari_metrics_helper import select_metric_collector_hosts_from_hostnames
from ambari_commons import OSCheck
from resource_management.libraries.resources.hdfs_resource import HdfsResource
import os


config = Script.get_config()
exec_tmp_dir = Script.get_tmp_dir()

install_dir = '/data/micro-services/8082-data-api-jar'

app_conf_dir = install_dir + '/conf'
app_bin_dir = install_dir + '/bin'

app_user = config['configurations']['ark-data-api-env']['ark-dataapi-user']
app_group = config['configurations']['ark-data-api-env']['ark-dataapi-group']

app_pid = 'lryhis.pid'
app_pid_path = app_bin_dir + "/" + app_pid

# ---------- server properties ------------
app_server_port = config['configurations']['ark-data-api-server']['dataapi.app.server.port']
app_log_file = install_dir + '/logs/application.log'

presto_host = config['configurations']['ark-data-api-server']['dataapi.presto.host']
presto_query_timeout = config['configurations']['ark-data-api-server']['dataapi.presto.query.timeout']

<!-- 省略部分 -->

db_host=config['configurations']['ark-data-api-server']['dataapi.db.host']
db_admin_user=config['configurations']['ark-data-api-server']['dataapi.db.admin.user']
db_admin_password=config['configurations']['ark-data-api-server']['dataapi.db.admin.password']
db_pool_size=config['configurations']['ark-data-api-server']['dataapi.db.pool.size']

其中config = Script.get_config() 会在当前目录找相关的configurations 配置文件
比如:

1
config['configurations']['ark-data-api-server']['dataapi.app.server.port']

代表 在configurations 目录下 ark-data-api-server文件中的 dataapi.app.server.port 配置

ambari参数配置文件:ark-data-api-server.xml 参考:

ark-data-api-server.xml
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

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration supports_final="true">

<property>
<name>dataapi.app.server.port</name>
<value>8082</value>
<final>true</final>
<description>服务启动端口</description>
</property>

<!--省略-->
<property>
<name>dataapi.db.admin.user</name>
<value>xxxx</value>
<final>false</final>
<description></description>
</property>
<!--密码设置property-type为PASSWORD,在ambari界面上就会特殊处理,不会直接展示-->
<property>
<name>dataapi.db.admin.password</name>
<value>xxxx</value>
<property-type>PASSWORD</property-type>
<final>false</final>
<description></description>
</property>

<property>
<name>dataapi.db.pool.size</name>
<value>20</value>
<final>false</final>
<description></description>
</property>

</configuration>

其中 supports_final="true"final=true 表示 该参数不可修改

配置文件模版:application.properties.jr

这个模版文件通过特殊占位符,在定义configure 方法中替换占位符,生成application.properties到指定目录。 而这个配置文件并不是全部配置项,而是一个覆盖的。前面说了启动时 指定了配置文件 为 classpath:/,$conf_file,这个配置文件会覆盖classpath 目录下的配置

package/scripts/ark_app_api.py
1
2
3
4
5
6
7
# 这里根据Templates目录下的.j2文件生成最终程序所需的配置文件
File(params.app_conf_dir + '/application.properties',
content=Template('application.properties.j2'),
owner=params.app_user,
group=params.app_group,
mode=0755
)

最后上一个模版文件:

application.properties.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server.port={{app_server_port}}

jdbc.presto.driverClassName = com.facebook.presto.jdbc.PrestoDriver
jdbc.presto.url=jdbc:presto://{{presto_host}}/hive/default
jdbc.presto.query.timeout={{presto_query_timeout}}

dbHost={{db_host}}
dbUser={{db_admin_user}}
dbPassword={{db_admin_password}}

jdbc.mysql.driverClassName =com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://${dbHost}:3306/database_xxx?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
jdbc.mysql.username=${dbUser}
jdbc.mysql.password=${dbPassword}
jdbc.mysql.maxActive = {{db_pool_size}}

spring.redis.host={{redis_host}}
spring.redis.port={{redis_port}}

# more

从这里可以看出来,特殊占位符为双大括号, 占位符里面的值为 params.py 文件内定义的。例如 app_server_port

开发插件后的部署

定义 stacks

与参考文章不一样的是,我的服务插件直接塞到了HDP目录下。所以stacks里只填写了服务信息在哪里,其他都是在common-services 里面定义好了。

common-services 和 stacks 处于同一目录

服务的定义配置文件都放在:

1
/var/lib/ambari-server/resources/common-services/ARK_DATA_API/0.0.1

stacks定义文件放在:

1
/var/lib/ambari-server/resources/stacks/HDP/2.0.6/services/ARK_DATA_API/metainfo.xml

metainfo.xml:

告诉ambari该插件相关信息映射到 common-services/ARK_DATA_API/0.0.1下的metainfo.xml

metainfo.xml
1
2
3
4
5
6
7
8
9
<metainfo>
<schemaVersion>2.0</schemaVersion>
<services>
<service>
<name>ARK_DATA_API</name>
<extends>common-services/ARK_DATA_API/0.0.1</extends>
</service>
</services>
</metainfo>

部署

将相关文件复制到ambari 的common-services目录和stacks目录 ,ambari-server restart 即可

参考:

https://my.oschina.net/naqin/blog/389720


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×