在上一篇文章中,
我用一个了案例展示了如何将外部系统的数据应用引入到salt里面,
以及通过自开发state扩展的方式实现底层逻辑,并用sls实现业务逻辑。
这次,我们将举例说明在涉及与外部系统进行数据交互的时候,如何利用salt对外部系统进行数据更新。
接下来就进入今天的案例
自动维护主机在jumpserver中的数据
jumpserver,就是所谓的跳板机。普通用户在申请好自己的服务器后,可以通过跳板机登陆自己的系统。
跳板机上面实现了很多安全逻辑,用以实现隔离与命令审计等功能。
这次的任务,我们需要做到以下几点:
- 能够将主机的信息自动的注册进jumpserver,在主机数据变更的时候,也能进行数据更新
- 在主机删除的时候,jumpserver的数据也能得到删除
首先,我们设想一下各种方案的可行性。我们可以采取如下的方案中的一种:
- 使用pillar + py renderer的方式update数据
- 自定义一个execution module, 并使用salt调度器周期性执行
- 自定义一个state module,把主要的逻辑写在module中,用sls去调用
- 使用sls调用写好的外部程序
对于第一种方案,我们已经在上一篇文章中使用过,既然渲染器能执行python代码,也能够直接执行update操作。但是问题在于在pillar的渲染器中,我们没办法动态的引用其他pillar里面的数据。这涉及到salt核心部分的执行逻辑。我们这次需要将aws pillar中获得的数据一部分导入到jumpserver中。因此不能同样的采用pillar渲染器去执行。
剩下的三种方案中,我更倾向在扩展的module中实现底层功能,把业务逻辑放置在sls中。底层module就像乐高积木的每一个单元,
而业务逻辑,就是把这些积木组装在一起。除了可读性以外,对于业务逻辑来说,主要要面对的问题在于经常的会变更,把业务逻辑写在扩展中,
需要将服务器端的module重新同步到客户端才能生效,而sls是实时生效且更轻量级,更适合快速开发的场景。基于这个考虑,最后一个方案更为合适。
接下来,我们可以考虑的实现方式是把对接jumpserver的api写在scripts中, 在sls中使用cmd.run去调用。但是最大的问题在于如何有效的进行安全隔离,让每个minion只能更新自己的数据,不能获得其他minion的数据,这会形成安全隐患。如果jumpserver 的api本身实现不了这些功能,那么可能需要重新做一个api server,
对jumpserver的api进行封装,实现安全的逻辑。这是可行的,虽然比较麻烦。
我们这边文章主要是基于salt现有的组件,所以肯定是以通过salt实现为最优先选择。条条大路通罗马,但是我们只选择最快捷的方式。
最后我决定采用的方式是使用sls + event -> reactor -> runner来实现这个功能
步骤如下:
编写salt runner用来对接jumpserver api
salt runner是在服务端执行的python程序,salt把runner中的某一个函数映射到salt的一组调用,并能够和salt的其他组件联动。
开发者几乎减去了所有不必要的代码,只需要关注业务逻辑即可。
因为涉及到业务逻辑,我将代码隐藏起来,只写了基本的逻辑。
runner的路径需要单独在master配置文件进行配置,需要注意的是,对于不需要使用 fileserver的,也就是通过salt://xx暴露出来的文件,
都不要放在salt fileserver的路径下面,避免出现安全上的隐患。
/srv/_runners/jumpserver.py
|
|
runner函数写好以后,可以通过salt-run module.function [args]
的方式来直接调用写好的runner
编写pillar,让客户端拿到jumpserver服务器的信息
和上一篇文章中用到的方法是一样的。
不过有一个问题特别值得注意,就是在缓存中我们选择了主机的ip地址为主键,这是因为对于我们的场景,
主机名变更的比ip地址更换的更加频繁。
而对于salt来说,我们使用主机名称作为主键。我们对主机名称使用了特别的编码,让主机名称包含有结构化信息。
方便salt进行定位。
主键不同,需要付出很多额外的代码。因此在设计主键时,要全方面的考虑到各种情况。
/srv/pillar/jumpserver/init.sls
|
|
在代码中,我们直接引用了写好的runner程序获得jumpserver的数据。
我们把缓存的逻辑处理放在pillar里面,get_host函数里面就可以不通过缓存。
通过自定义grains module挖掘minion上的信息
jumpserver需要知道minion的端口号作为其中一个参数。我们可以使用grains,先将这部分数据挖掘出来。
而不必每次都去执行对应的逻辑,如果其他任务也用到这部分数据,也可以从grains中提取
/srv/salt/_grains/sshd.py
|
|
在sls中使用py render实现复杂逻辑
我们在sls中,需要实现的是通过event.send将信息通过salt的事件系统传送到salt-master进行处理。
sls默认的渲染器是jinja + yaml. 这本身可以实现大部分的逻辑功能,但是在某些异常复杂的场景下,依然会显得力不从心。
jinja只是一个模板语言,表达能力有限,用py渲染器反而会简单许多。
/srv/salt/jobs/jumpserver/init.sls
|
|
我们将grains中的数据,以及从pillar中取出来的数据,同现有的jumpserver的数据进行比较。
因为我们使用的云平台不同,所以获取数据的渠道也有别。aws 的pillar的数据,就是在我们上一篇文章中介绍的做法。
最后根据实际情况,使用jumpserver/add_host或者jumpserver/reset_host。
配置并编辑reactor
salt reactor是一个快捷的事件驱动框架。我们只需要定义一个event和reactor的映射关系,就可以构建一个事件处理系统
reactor配置:
/etc/salt/master.d/reactor.conf
|
|
reactor编写:
/srv/reactor/jumpserver/add_host.sls
|
|
/srv/reactor/jumpserver/reset_host.sls
|
|
在reator中,我们将事件直接映射到salt runner。如果有复杂的场景,我们可以把reactor映射到salt的orchestrater系统。
具体用法,可以参考salt官方文档,不再赘述。
这里面有几个问题值得注意一下。 我们用到一个_syndic_handle的参数,这个原因我会再起一篇文章进行描述,暂且不表。
还有一个就是tojson的 jinja过滤器。这是salt2018.3引入的过滤器,用以解决sls不直接支持字典的问题。
处理主机删除后的jumpserver的垃圾清除
这是最后一步工作了。因为主机已经删除了,所以肯定不能通过sls来调用了。
还记得我们在上一篇文章有一个垃圾处理的外部脚本,我们只需要把runner中的remove_host函数附加到主机删除的过程里面就可以了
总结
在本次的案例中,我们采用的技术方案有
- 使用salt runner作为service,快速的搭建一个自动处理服务
- 使用在sls中使用py renderer来实现比较更加复杂的逻辑
- 使用自定义grains,将客户端数据采集到salt中,供salt其他任务使用
- 使用reactor解决安全问题,每个发出的事件,都由salt认证主机id。不过salt event系统中,并不包含minion的ip地址,因此没办法直接认证ip地址。需要对salt event进行一定的改良