SaltStack系列(三)之state相关介绍
一、管理对象
saltstack系统中管理对象叫做Target,在master上可以采用不同的Tatget去管理不同的minion。这些Target都是通过去管理和匹配Minion的ID来做一些集合。
1.1 -E, --pcre : 正则匹配
# salt -E '[a-z].*' test.ping #直接就是匹配字母开头的minion
# salt -E 'a.*' test.ping #匹配a开头的minion
# salt -E '(a|z).*' test.ping #匹配a或者z开头的minion,切记是开头而不是包含。
1.2 -L : 列表匹配
# salt -L agent1.salt,zwidc_kvm_192.168.1.104 test.ping #同时让两个minion去执行,多minion之间用逗号隔开。
1.3 -G : grains匹配
minions的Grains信息时在Minions服务启动的时候汇总给Master的,Grains是saltstack组建中非常重要的,因为在配置部署的过程中会经常使用它,Grains是saltstack记录minion的一些静态信息的组件,grains里面记录着每台minion的一些常用属性如CPU、内存等,可以通过grains.item查看某台minion的所有grains信息。
# salt -G 'os:CentOS' test.ping #让minion端操作系统是CentOS去执行,当然也支持正则表达式的方式
# salt -G 'os:C*' test.ping #如匹配操作系统是C开头的
# salt -G 'osmajorrelease:[1-9]' test.ping #匹配系统版本是数字的。
1.4 -N : 组匹配
# cat /etc/salt/master |grep groups #如我们定义了一个centosgroups组
nodegroups:
centosgroups: 'G@os:CentOS'
# salt -N centosgroups test.ping #就可以指定让某个组的minion去执行。
1.5 -C : 复合匹配
# salt -C 'G@os:CentOS and G@osrelease:6.4' test.ping #让os是CentOS并且系统版本是6.4的去执行
# salt -C 'G@os:CentOS or G@osrelease:6.5' test.ping #让os是CentOS或者系统版本是6.5的去执行
# salt -C 'G@os:CentOS and G@osrelease:6.4 and E@zwidc*' test.ping #让zwidc的minion并且操作系统是CentOs6.4的去执行
1.6 -S : CIDR网段匹配
# salt -S '192.168.1.0/24' test.ping #192.168.1.0/24是一个指定的CIDR网段,这里CIDR匹配的IP地址是minion连接master4505端口的来源地址。
博文来自:www.51niux.com
二、Grains介绍
Grains上面已经介绍过了,类似于facter。简单的说就是把minion端在启动的时候把自身的的各种属性信息采集汇报给master端。
2.1 grains模块的一些命令行的使用方法:
# salt '*' grains.ls #可以查看有哪些属性可以查看,显示的是属性的名字,类似于os之类的。
# salt '*' grains.item os osrelease oscodename #知道了属性的名称,我们就可以查看属性的值,这里就是查看os,osrelease,oscodename这三个属性的值,多属性用空格隔开。
# salt '*' grains.items #这种就是将minion端属性以及属性的值全打印出来。
2.2 通过minion端配置文件定义grains:
minion端的操作:
# cat /etc/salt/minion.d/position.conf #在默认的/etc/salt/minion.d目录下面创建一个position.conf,这个文件的目的是来自定义服务器所在的位置的属性信息。下面就是标准格式。
grains: #以grains开头
roles: #设置一个属性叫roles,下面如果多条就像下面一样- value值,这里的意思是标注服务器属于什么业务
- webserver
- memcache
deployment: bj-zw-datacenter #这里是标注所在机房的位置
cabinet: B13 #标注所在机柜的位置
cab_u: 14-15 #标注所在机柜的U位
# service salt-minion restart #上面已经说了,grains信息是在minion端启动的时候才会发送,所以要重启minion端。
master端的查看:
# salt 'agent1.salt' grains.ls #用这个查看会发现我们自定义的哪几个grains的属性已经出现了。
# salt 'agent1.salt' grains.item roles deployment cabinet cab_u #查看一下对应的值
2.3 命令自定义grains
# salt 'agent1.salt' grains.append the_person 'chaishao' #通过grains.append方法为agent1.salt机器添加了一个属性,the_person使用人,后面跟的值是chaishao。
还可以使用其他的方法,如grains.setvals来定义多个属性,或者还可以删除自定义的grains。这都是立马生效并永久生效的。因为它修改的是minion端的/etc/salt/grains 文件。
三、Pillar介绍
Pillar是saltstack组件中非常重要的一个,是数据管理中心,经常配合states在大规模的配置管理工作中使用它,它的主要作用是存储和定义配置管理中需要的一些数据。它的定义存储格式跟grains类似,都是YAML格式。
下面的操作都是在master端的操作:
# mkdir /srv/pillar #这是master配置文件里面#pillar_roots:默认指定的路径。默认不存在需要手工创建。
# cat /srv/pillar/top.sls #top.sls是配置管理的入口文件,一切都是从这里开始,这里是默认位置
base: #top.sls 默认从 base 标签开始解析执行,下一级是操作的目标,可以通过正则,grain模块,或分组名,来进行匹配,再下一级是要执行的state文件,不包换扩展名。
'*':
- pkgs
# cat /srv/pillar/pkgs.sls #一个简单的根据minion端的grains信息,动态的设置apache软件包对应的name名称。
pkgs:
{% if grains['os_family'] == 'RedHat' %}
apache: httpd
{% elif grains['os_family'] == 'Debian' %}
apache: apache2
{% elif grains['os'] == 'Arch' %}
apache: apache
{% endif %}
# salt '*' pillar.item pkgs
有结果来看,pillar数据是在Salt master上生成的并被安全地分布到minions上。Salt当定义pillar的时候,不必限制在sls文件,也可以从外部资源获得数据,我们可以把Pillar数据。pillar中最强大的抽象之一就是将states参数化的能力。
四、States介绍
States是SaltStack中的配置语言,在日常进行配置管理时需要编写大量的States文件。如安装软件啊,更改配置啊等,就需要编写states sls文件(描述状态配置的文件)去描述和实现这些功能。编写states sls文件一般是是YAML语法格式,也支持使用Python语言来编写。
4.1 查看相关的states内容
# salt 'agent1.salt' sys.list_state_modules #查看minion支持的所有states列表。
# salt 'agent1.salt' sys.list_state_functions file host #查看file和host的所有function,多模块用空格隔开
# salt 'agent1.salt' sys.state_doc file #查看指定模块的function的用法与例子,日常编写states按照例子编写便可。
# salt 'agent1.salt' sys.state_doc file.managed #查看file.managed的详细用法与例子,这也是我们经常用到的function。
4.2 编写一个简单的sls文件并执行
# cat /srv/salt/test1.sls #编写了一个简单的file.managed的sls文件
/tmp/test1.conf: #state ID,全文件唯一,标签定义,如果模块没有跟-name,默认用的ID作为-name
file.managed: #file state的manage function,状态定义,指定引用哪个模块的方法函数。
- source: salt://files/test1.conf #文件来源(salt://默认代表了根目录也就是states的工作目录也就是/srv/salt。)
- user: root #文件的属主,剩下的这些都是file.managed里面的参数。
- group: root #文件的属组
- mode: 0644 #文件的mode
# mkdir /srv/salt/files/ #因为我们的source指定的是salt://files/,所以源目录的绝对路径应该是/srv/salt/files/下面的。
# touch /srv/salt/files/test1.conf #创建我们要发送的文件,你也可以写点内容。
# salt '*' state.sls test1 #top.sls这个入口文件不是必须存在的,但是生产是肯定要存在的,只是我们这里测试top.sls可以暂时不存在。用state.sls来进行测试,state.sls默认的运行环境是base,state.sls并不读取top.sls,所以state.sls需要单独执行哪些sls的话,需要自定义。这里我们让其执行test1.sls这个文件。
下面是测试截图(下面是测试结果正确的截图):
#如果你源文件没有发生改变,再次执行的话,Comment会提示你这个文件是正确的。Changes:下面没内容。Succeeded: 1,这就是区别,也就是不更新
博文来自:www.51niux.com
五、SLS文件介绍
SLS(代表SaLt State文件)是Salt State系统的核心。SLS描述了系统的目标状态,由格式简单的数据构成。这经常被称作配置管理。
5.1 YAML编写技巧
因为我们用YAML格式来编写sls文件,所以掌握编写技巧利于后面少出错。
5.1.1 什么是YAML?
YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。语法很简单,结构通过空格来展示,项目使用“-”来代表,键值对使用“:”分隔。
它的基本语法规则如下:
大小写敏感,以“:”来分隔key和value。对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)。
使用固定的缩进风格表示层级关系。缩进时不允许使用Tab键,只允许使用空格,一般每个缩进级别由两个空格组成。
# 表示注释,从这个字符一直到行尾,都会被解析器忽略。
想要表示列表项,使用一个短横杠加一个空格。多个项目使用同样的缩进级别作为同一列表的一部分。列表可以作为一个键值对的value。
5.2 top.sls
top.sls是配置管理的入口文件,这个很重要在生产中很重要,默认存放在/srv/salt/目录。默认从base标签开始解析执行,下一级是操作的目标也就是要对哪些主机进行操作,可以通过正则、grain模块或分组名等来进行匹配,然后再下一级是要执行的state文件(不包含.sls扩展名)。
5.2.1 正则匹配示例:
# cat /srv/salt/top.sls #执行的test1还是我们上面编写的那个简单的test1.sls
base:
'agent*': #这里定义了只有是注册的时候是agent开头的minion端的客户端才能去执行test1里面的配置管理
- test1 #这里指的是同目录下的一个test1.sls。只有这里top.sls制定了,我们在master执行:# salt '*' state.highstate test=True的时候才会被引用到。
# cat /srv/salt/top.sls #如果你不是指定*所有来执行test1,只是想让多个匹配规则去执行test1的话,可以像我下面这样写。
base:
'agent*':
- test1
'zwidc*':
- test1
# cat /srv/salt/top.sls #但是上面那种写法显然要low一点,可以- match: pcre,匹配正则表达式的方式来匹配对应的主机。
base:
‘(agent|zwidc)*’:
- match: pcre
- test1
5.2.2 通过分组名进行匹配示例:
# cat /srv/salt/top.sls
base:
centosgroups: #这里就是我们上面在master配置文件里面定义的那个组名
- match: nodegroup #这句话是必须要有的,指定以组匹配
- test1
5.2.3 通过grain模块匹配的示例:
# cat /srv/salt/top.sls #同上-match: grain也是必须要带的,指定让其按照grain进行匹配,指定让os是Centos的操作系统来执行。
base:
'os:CentOS':
- match: grain
- test1
5.2.4 通过复合匹配的示例:
# cat /srv/salt/top.sls #上面的grain模块匹配可能满足不了我们的匹配条件,我想要更加精确的让所有Centos6.4的操作系统去执行,就是我下面的设置。
base:
'G@os:CentOS and G@osrelease:6.4':
- match: compound
- test1
5.2.5 某一个网段匹配示例:
# cat /srv/salt/top.sls
base:
'192.168.1.0/24':
- match: ipcidr
- test1
5.3 state.highstate
# salt '*' state.highstate #master将会指导所有的目标minions运行 state.highstate。当minion执行highstate,它将会下载top文件中匹配的内容,minion将表达式中匹配的内容下载、编译、执行。一旦完成,minion将返回所有的动作执行结果和所有更改
# salt '*' state.highstate test=True #只是测试执行,类似于模拟,不会在minion真正执行,便于我们编写测试时使用。
5.4 SLS文件命名空间
-
SLS文件的扩展名.sls在state文件里面将被省略,上面已经展示到了,如test1.sls在文件里面就变为了test1.
-
子目录可以更好的进行组织架构,每个子目录都由一个点来表示,如/srv/salt/init/dns.sls在调用时就可以写为init.dns。
-
如果子目录创建一个init.sls的文件,引用的时候仅指定该目录即可,(例如test1/init.sls可以简称为test1)
-
如果一个目录下同时存在test1sls和test1/init.sls,那么test1/init.sls将被忽略,sls文件引用的test1将只引用test1.sls。
5.5 state的层级关系
include:将别的SLS添加到当前文件中,所以可以require或watch被引用的SLS中定义的内容,还可以extend覆盖其内容。include语句使得state可以跨文件引用。使用include相当于把被引用的内容文件添加到自身。
extend:扩展被引用的SLS数据。不需要重头写新的SLS,可以先直接include sls文件,然后在其基础上增加或者覆盖内容。
include示例:
# cat /srv/salt/top.sls #我们只引用了一个test1.sls文件
base:
'192.168.1.0/24':
- match: ipcidr
- test1
# cat /srv/salt/test1/init.sls
include: #格式就是include: 下面用- 空格 后面要加载的sls文件
- test1.test2 #这里我们虽然跟init.sls文件在同一个目录下面,但是根目录是/srv/salt,所以这里要用test1.test2来表示test1目录下面的test2.sls文件
/tmp/test1.conf: #剩下的就是test1的内容了。
file.managed:
- source: salt://files/test1.conf
- user: root
- group: root
- mode: 0644
- backup: minion
# cat /srv/salt/test1/test2.sls #写了一个很简单的发送文件的例子,用test2.conf和test1.conf区别开
/tmp/test2.conf:
file.managed:
- source: salt://files/test2.conf
- user: root
- group: root
- mode: 0644
- backup: minion
# salt '*' state.highstate test=True #master端执行测试查看效果,从结果查看test2.sls确实被加载了:
extend示例:
覆盖值的示例:
# cat /srv/salt/test1/init.sls
include:
- test1.test2
/tmp/test1.conf:
file.managed:
- source: salt://files/test1.conf
- user: root
- group: root
- mode: 0644
- backup: minion
extend: #extend: 下一行是test1.test2的ID号,所以/tmp/test2.conf这个文件是不能变的,这就是这个ID号是不能变的不然就找不到要去修改的标识了。
/tmp/test2.conf: #这个ID号是不能变的,是为了告诉extend去修改哪个ID下面的内容。
file.managed: #因为我们配置的比较简单,就file.managed这一个模块函数,这里就是指定要对哪个模块函数进行修改。
- source: salt://files/test3.conf #也就是file.managed下面的一些参数对应的值,这里我们将source从要下载test2.conf改为了下载test3.conf
# salt '*' state.highstate #master端执行让minion去执行同步管理任务。从下方的可以看出,客户端/tmp/test2的文件内容发生了变化。
添加参数的示例:
# cat /srv/salt/test1/init.sls #其实就是test2.sls里面没有定义- watch,所以这里就相当于是添加了。
include:
- test1.test2
extend:
/tmp/test2.conf:
file.managed:
- watch:
- file: /tmp/test1.conf
/tmp/test1.conf:
file.managed:
- source: salt://files/test1.conf
- user: root
- group: root
- mode: 0644
- backup: minion
#Extend使得Salt的SLS更加灵活。为什么SLS能够做Extend呢?SLS中的文件仅仅是结构化的data而已,在处理SLS时,会将其中的内容解析成Python中的dict(当然这个dict中会嵌套dict和list)。修改test2 watch的内容,相当于往list里面添加一个元素;修改test2.conf文件的下载路径相当于修改dict中的某个key对应的值。在extending时,会附加加require/watch的内容,而不是覆盖。
5.6 state的逻辑关系
match : 配模某个模块,比如 match: grain match: nodegroup
require: 依赖某个state,在运行此state前,先运行依赖的state,依赖可以有多个
watch : 在某个state变化时运行此模块,watch除具备require功能外,还增了关注状态的功能。
order : 优先级比require和watch低,有order指定的state比没有order指定的优先级高,假如一个state模块内安装多个服务,或者其他依赖关系,可以使用
5.7 state的逻辑关系示例
require和require_in简单示例:
# cat /srv/salt/test1/init.sls #这是require的示例,就是表示自己依赖于谁。
vim: #定义了第一个ID为vim
pkg: #然后引用的是pkg模块
- installed #引用的是installed函数
- name: vim-enhanced #因为vim的软件包名称并非vim,所以这里要加上vim包组的名称
/tmp/vimrc:
file.managed:
- source: salt://files/vimrc
- require: #- require: 依赖于下面的ID操作,如果下面的ID操作了或者说要操作的内容已经存在了,本ID也就是/tmp/vimrc才会去执行
- pkg: vim #依赖于id为vim的pkg状态,多依赖的话,下面照着这个来多行就可以了。
#成功的测试就不截图了,下面我估计讲minion端的DNS解析关闭掉,然后执行查看一下错误截图:
# cat /srv/salt/test1/init.sls #这是require_in的示例,表示谁依赖于我
vim:
pkg:
- installed
- name: vim-enhanced
- require_in: #表示下面的id代表的操作依赖于我是否能执行成功,在这里就是表示我本次是否要安装成功或者要安装的软件包组是否存在。
- file: /tmp/vimrc
/tmp/vimrc:
file.managed:
- source: salt://files/vimrc
watch和watch_in的示例:
# cat /srv/salt/test1/init.sls #这是watch的示例,表示我检测谁的变化,如果它变化执行了我就跟着执行,它要不执行我也不执行。
ntpd: #定义了一个ID是ntpd的配置规则
service.running: #这里引用了service.running函数- enable: True默认就是这个,这里就不加了。
- watch: #这里它是监听ID为file: /etc/ntp.conf,如果下方的状态没有发生改变,那么我这个重启规则就不会执行,那么就不会执行服务重启操作。
- file: /etc/ntp.conf #也就相当于如果minion端的ntp.conf或者master端的salt://files/ntp.conf的文件没有发生改变,也就不会发生文件下发操作,也就可以理解为如果没有发生文件更改。
/etc/ntp.conf:
file.managed:
- source: salt://files/ntp.conf
下面是文件状态没有发生改变的截图(一切相安无事):
下面是我更改了下master段的源文件,也就是ntp.conf,在其上面加了一行注释,再次查看,ntpd服务重启了:
# cat /srv/salt/test1/init.sls #这是watch_in的示例,表示我改变了执行了我就通知依赖于我变化的状态规则让他们也去执行。
ntpd:
service.running #主要是这里要注意,上面的例子有冒号而这里没有,这是因为当不将任何参数传递给状态时,冒号必须被省略。
/etc/ntp.conf:
file.managed:
- source: salt://files/ntp.conf
- watch_in: #这就是如果执行了下发了ntp.conf操作,我就通知ID是ntpd的server状态,让其执行重启服务操作。
- service: ntpd
5.8 state的执行顺序
state的执行是无序,那个无序是指执行我们写的那个sls是无序的,一般执行的时候会根据sls文件里面状态ID:写的顺序从上到下执行,正是因为那个无序,salt保证每次执行的顺序是一样的,就加入了state order。
# salt 'agent1.salt' state.show_highstate #就是查看状态的执行顺序从高到底排序结果
# salt 'agent1.salt' state.show_lowstate #查看状态的执行顺序从低到高排序结果
通过观察会发现一个字段order,是order决定了状态之间的先后执行顺序,因为salt默认会自动设置order,从10000开始。可以通过设置master配置文件参数添加一行:state_auto_order: False来关闭master的自动设置order。
Order的设定:
-
include 被include的文件Order靠前,先执行。
-
手动定义order字段,order的数字越小越先执行从1开始,-1是最后执行。
下面是order手工设置的一个小例子:
# cat /srv/salt/test1/init.sls #为了演示效果,我打乱了状态ID:在sls中的上下顺序:
ntpd:
service.running:
- enable: True
- order: 3
ntp:
pkg.installed:
- name: ntpdate
- order: 1
/etc/ntp.conf:
file.managed:
- source: salt://files/ntp.conf
- order: 2
# salt 'agent1.salt' state.highstate #执行一下查看一下效果(测试的时候可以把order那一行注释掉再次测试查看一下效果就很有对比性。也可以# salt 'agent1.salt' state.show_highstate直接查看):
博文来自:www.51niux.com
六、Jinja使用介绍
6.1 Jinja介绍
Jinja是基于Python的模板引擎,功能比较类似于PHP的Smarty。Salt默认使用yaml_jinja渲染器。yaml_jinja的流程是选用jinja2模板引擎处理SLS,然后再调用YAML解释器。我们需要了解一点Jinja语法知识,因为在配置管理中经常会用到,这也是salt能真正实现高度自动化配置的一个重要技能。
Jinja的使用分为三个步骤:
-
File状态使用template参数 - template: jinja。
-
模板文件里面变量使用{{名称}},比如{{PORT}}.
-
File状态模块里面指定变量列表。
6.2 Jinja使用示例:
# cat /srv/salt/jinja1/init.sls #引用jinja灵活配置的小例子。
{% set iplist = grains['ipv4'] %} #首先{% set 变量名 = 给变量赋值 %},这种格式可以设置一个格式。这里我们引用了grains获取minion端的ipv4值的方式,得出来一般是一个列表也就是一个数组,127.0.0.1在第一位。
/tmp/minion.conf:
file.managed:
- source: salt://files/minion.conf.jinja #这里如果你是要使用到jinja模式,文件里面有好多变量的话,最好有个后缀,比较容易区分。
- template: jinja #这就是介绍里面说的第一步,使用这个参数,说明自己使用的是jinja模板。
- defaults: #默认将值传递给模板
IP_ADDR: {{iplist[1]}} #IP_ADDR是minion.conf.jinja里面定义的一个变量,后面的赋值用{{}}包起来,去iplist这个列表的第二个值。这个根据自己情况再进行修改,我这就是为了演示简单写了下。
# cat /srv/salt/files/minion.conf.jinja #这是模板文件,就写了很短的一行,为了掩饰效果。
ipaddr:{{IP_ADDR}}
# salt 'agent1.salt' state.highstate #master端执行一下,成功了,就不接图了。下面我们查看一下客户端的配置文件里面的IP是不是跟本机是一致的。
6.3 Jinja变量的使用
Jiaja变量使用很灵活的,下面大概总结一下:
变量使用单纯的变量赋值(上面的例子也简单的用了下)(除了模板引用以外sls文件也可以直接引用,下面就是非模板的变量引用):
{% set keepalived_tar = 'keeplived-1.2.17.tar.gz' %} #用{%...%}符号定义变量
{% set keepalived_source = 'salt://modules/keepalived/files/keepalived-1.2.17.tar.gz' %}
keepalived-install:
file.managed:
- name: /usr/local/src/{{ keepalived_tar }} #这里用{{...}}引用变量赋值
- source: {{ keepalived_source }}
变量使用Grains(上面的例子也有列出):
IP_ADDR: {{grains['ipv4'][1]}} #上面的变量引用,可以直接这样写的,更简洁一点。
变量使用执行模板,也就是使用salt命令返回的值(因为grain只有再minion重启的时候才会将信息上交一份,要是在重启之前有些配置发生了更改,难免匹配不准):
# cat /srv/salt/jinja1/init.sls
/tmp/minion.conf:
file.managed:
- source: salt://files/minion.conf.jinja
- template: jinja
- defaults:
IP_ADDR: {{salt['network.ip_addrs']}} #一个是获取IP地址,salt代表是使用salt命令['要使用的funtion方法']
MAC: {{ salt['network.hw_addr']('eth0') }} #一个是获取MAC地址,后面('eth0')是因为有的方法后面好跟参数,如network.hw_addr后面就需要跟接口名称
# cat /srv/salt/files/minion.conf.jinja #注意模板这里写了几个变量,sls文件里面就要写结果变量应该映射的值,不然会报错说Jinja某个变量未定义
ipaddr:{{IP_ADDR}}
mac:{{MAC}}
变量使用Pillar来定义变量的值(这个就是首先定义好了pillar,这里来直接引用下就可以了):
{{ pillar['apache']['PORT'] }}
6.4 Jinja逻辑关系
Jinja还主要用来给状态增加逻辑关系,如for循环,或者if判断,也是经常使用到的。
salt,grains,pillar是salt中jinja里面的三个特殊字典,salt是包含所有salt函数对象的字典,grains是包含minion上grains的字典,pillar是包含minion上pillar的字典。上面已经介绍了如何使用了。
if...elif...endif的示例:
# cat /srv/pillar/pkgs.sls
apache:
pkg.installed: #这是根据系统的不同,判断安装的软件名称。如果有变量就放到{{}}中
{% if grains['os_family'] == 'RedHat' %} #jinja中判断,循环等标签是放在{% %}中
- name: httpd
{% elif grains['os_family'] == 'Debian' %}
- name: apache2
{% elif grains['os'] == 'Arch' %}
- name: apache
{% endif %} #也会有结束标签{% end** %}
#当然还支持:{% if %}...{% elif %}...{% else %}...{% endif %}之类的写法。
for循环的示例:
{% set userlist = ['yunwei','cacti','www'] %}
{% for user in userlist %}
{{ user }}:
user.present:
- shell: /bin/bash
- home: /home/{{ user }}
{% endfor %}