【Saltstack】
Saltstack是一个服务器集中管理中心平台,可以帮助管理员轻松的对若干台服务器进行统一操作。类似的工具还有Ansible,Puppet,func等等。相比于这些工具,salt的特点在于其用python实现,支持各种操作系统类型,采取了C/S架构,部署方便简单, 且可扩展性很强。
本文就旨在简单地说明salt的使用方法,参考了Python自动化运维那本书
■ salt安装和简单配置
以一台CentOS7.4为例,开始说明。
salt安装可以方便的使用yum进行安装。RHEL官网yum源目前还没有salt的安装包,因此需要先安装epel源,不过一般使用过一段时间的CentOS系统的话八成都是有装这个源的。如果没有可以到相关网站下载rpm包安装。另外salt是由python实现的,依赖python环境和和一些python的扩展模块。一般linux都会自带python这就还好,扩展模块的话如果是通过yum安装,也会自动装上,无需担心。
首先说明salt的两个概念,master和minion。master是主控端,相当于所有salt控制机器指令的出发点。minion就是被控端。在这里的示例中,我们这台服务器又当主控又当被控。
在主控端需要安装yum install -y salt salt-master
在被控端需要安装yum install -y salt salt-minion
由于salt并不是依赖于SSH进行通信的,所以要开通额外的端口,默认配置下master走4505,minion走4506这两个端口。要注意网络控制和防火墙等不能对这两个端口进行控制。
安装完成之后就可以启动服务来看下是否安装成功。systemctl start salt-master/systemctl start salt-minion。之后在看下netstat -ntlp,如果两端口都顺利开启就说明安装成功。
● 配置
此时的salt仅仅是完成了最基本的安装,需要对配置文件修改过后才能使用。主控端配置文件是/etc/salt/master,被控端配置文件是/etc/salt/minion。配置文件的格式是YAML,下面对主控和被控配置文件进行一些修改:
##### /etc/salt/master ##### #修改master绑定的通信IP interface: 127.0.0.1 #自动认证模式,避免手动运行salt-key命令 auto_accept: True #saltstack文件根目录位置指定 file_roots: base: - /srv/salt ##### /etc/salt/minion ##### #指定master的IP master: 127.0.0.1 #修改minion的识别ID,通常要保证一个salt网络中的唯一性 id: TestMinion
然后systemctl restart salt-master/salt-minion来重启两个服务
我们可以用test.ping这个模块来进行服务打通的验证:
[root@localhost salt]# salt '*' test.ping TestMinion: True
上面配置中如果没有配置auto_accept项的话,那么需要进行手动的证书认证操作。这个涉及到了salt-key命令:
salt-key -L 列出所有已认证和未认证的被控端ID
salt-key -D 删除所有已认证证书
salt-key -A 自动接受所有ID的证书
salt-key -a id 接收指定ID的minion的证书
salt-key -d id 删除指定ID的minion的证书
一般而言,master必须有minion的证书的情况下才能顺利地进行通信
■ 通过saltstack远程执行命令
通过salt来执行远程命令的基本格式是:
salt '<操作目标>' <方法> [参数]
比如查看所有主机的内存使用情况就可以:
[root@localhost salt]# salt '*' cmd.run 'free -m' TestMinion: total used free shared buff/cache available Mem: 488 261 6 2 219 192 Swap: 1023 1 1022
当然这里使用salt '*'和salt 'TestMinion'效果是一样的,因为只有这一个minion的话。
除了'*'(通配符,指代所有)和'指定ID'外,salt还支持通过以下方式来选出若干个minion作为被操作的对象:
-E或者--pcre 正则表达式,如salt -E '^Test.*'选择了所有Test开头的ID的minion
-L或者--list 如salt -L 'TestMinion1,TestMinion2,TestMinion3',将各个ID通过逗号隔开
-G或者--grain 根据被控主机的grains进行选取,格式为'<grain value>:<glob expression>'
-I或者--pillar 根据被控主机的pillar进行选取,格式为'<object name>:<object value>'
-N或--nodegroup nodegroup是在主控端配置文件中配置的nodegroups,将各个minion根据配置分组的规则。这个参数自然是可以选择一个特定分组的所有minion进行操作。关于master中的nodegroup可以像这样配置:
nodegroups: web1group: 'L@TestMinion1,TestMinion2' web2group: 'L@TestMinion3,TestMinion4'
-C或--compound 允许用and,or,not关键字来连接多个条件,需要注意的是not关键字不能作为第一个条件,当not要作为唯一的条件时可以这样做: salt -C '* and not E@^Test' test.ping。这个命令就是对所有非Test开头的minion进行匹配
-S或--ipcidr 根据IP或者IP段进行匹配,比如salt -S 192.168.0.0/16 test.ping
复合条件:当需要把一个匹配条件嵌入另一个条件的表达式时采用<表示条件的大写字母>@<条件描述>,比如-C说明中的not E@^Test中的E就是指出了后面这个条件是-E,也就是正则表达式条件,^Test则是正则的内容。类似的我们可以'L@TestMinion1,TestMinion2','G@os:Centos'这样子。
■ saltstack的常用模块和API
saltstack自带了很多常用功能的模块,涉及到了OS的基础功能和常用工具的支持等。更多的模块可以参考官网文档,在本地saltstack上可以通过salt '*' sys.list_modules来查看支持的所有模块。
而API则是留给开发人员使用的saltstack接口,比如之前我们在命令行中用到的test.ping功能,其实现的原理就是:
import salt.client client = salt.client.LocalClient() ret = client.cmd('*','test.ping') print ret #放回的ret是一个字典格式的字符串: '{"estMinion":True}'
大多数时候,我们可以通过python程序的方式来调用salt的功能,以将其嵌入我们的程序,非常给力!
下面就各个模块说明使用方法和API
● Archive模块
功能:实现了系统层面的压缩包调用,支持gzip,tar,rar,unzip,unrar等等命令
示例:
#解压所有机器上/tmp/sourcefile.txt.gz包 salt '*' archive.gunzip /tmp/sourcefile.txt.gz #压缩所有机器上/tmp/sourcefile.txt salt '*' archive.gzip /tmp/sourcefile.txt
API调用示例:
client.cmd('*','archive.gunzip',['/tmp/sourcefile.txt.gz'])
● cmd模块
一般意义上实行远程命令执行的模块,默认情况下是以minion端的root权限进行命令的执行的,需要谨慎!
示例:
#查看内存 salt '*' cmd.run 'free -m'
据说这个远程执行命令的原理是首先将我们要运行的命令写入本地的一个文件,然后通过salt分发给其他被控端的服务器,然后再将各个服务器上的这个脚本通过cmd.script模块运行。
API调用示例:
client.cmd('TestMinion','cmd.run',['free -m'])
刚才archive模块也是这样が,这里的第三个参数是个列表,这说明我们可以进行多个命令的输入。
● cp模块
顾名思义,cp模块是用来进行远程文件和目录的复制,下载等操作的,类似于SFTP
示例:
#指定被控端主机的/etc/hosts文件复制在被控端本地的salt cache目录,默认是/var/cache/minion/localfiles/,被复制的文件会连同其到根目录的整个路径都复制过去,也就是说这个命令执行之后看到的是xxx/localfiles/etc/hosts salt '*' cp.cache_local_file /etc/hosts #将主服务器file_roots下的相关目录复制到被控主机的/minion/dest目录下 salt '*' cp.get_dir salt://path/to/dir /minion/dest # 将主控服务器file_roots下相关的文件复制成被控主机的/minion目录下的dest文件 salt '*' cp.get_file salt://path/to/file /minion/dest # 将URL内容下载到被控主机的指定位置 salt '*' cp.get_url http://www.slaghdot.org /tmp/index.html
从上面的说明中可以看出来,salt://这个相当于是替换了master配置中的file_roots的配置项,后面只要写些相对路径即可。另外get_dir,get_file这些模块名,说是get(下载),其实是put(上传),初学时容易混淆,应该搞清楚。那如果想要下载的话是要用什么?是用file.copy模块。
还有一些细节问题,比如在get的时候,其实salt用了shutil模块的一些内容,所以remote_path应该写包含目录/文件名的完整路径而不是目录结尾的路径。
API调用:
client.cmd('*','cp.get_file',['salt://path/to/file','/minion/dest'])
● cron模块
管理目标被控端上的一些crontab任务
示例:
# 查看被控主机的root用户的crontab清单,如果目标用户没有crontab任务的话那么会导致返回码是1,所以salt这边就返回non-zero exit code错误 salt 'TestMinion' cron.raw_cron root # 为被指定的主机的root用户添加crontab任务 salt 'TestMinion' cron.set_job '*' '*' '*' '*' 1 '/usr/bin/echo "Called"' 'Some Comment' # 说明:前几个参数分别对应crontab -e编辑时的各个字段,最后一个参数其实是salt可以自动为这个crontab添加一些注解 # 删除指定主机的root用户的指定crontab任务 salt 'TestMinion' cron.rm_job '/usr/bin/echo "Called"' # 如果删除成功,返回removed,如果没有找到相关任务返回absent
API调用
client.cmd('TestMinion','cron.set_job',['root','*','*','*','*','1','/usr/bin/echo "Called"'])
● dnsutil模块
实现被控主机通用DNS的一些操作,由于网络这方面不熟悉,就不写出来了。
● file模块
被控主机的文件常见操作。包括读写,权限,查找,校验等等,
示例:
# 校验所有被控端的/etc/fstab文件md5值是否是指定值,返回True和False salt '*' file.check_hash /etc/fstab md5=xxxxxxxxx # 校验所有被控端的指定文件的hash值,算法除了md5还支持sha1,sha244,sha256等等 salt '*' file.get_sum /etc/passwd md5 # 修改被控端文件的用户所属信息,下面这条相当于chown root:root /etc/passwd salt '*' file.chown /etc/passwd root root # 复制被控主机的/path/to/src文件为/path/to/dest文件 salt '*' file.copy /path/to/src path/to/dest # 检查文件/目录是否在被控端上存在 salt '*' file.directory_exists /etc salt '*' file.file_exists /etc/passwd # 查询文件相关信息 salt '*' file.stats /etc/passwd # 获取/设置指定文件的权限 salt '*' files.get_mode /etc/passwd salt '*' files.set_mode /etc/passwd 0644 # 创建目录 salt '*' files.mkdir /opt/test #用sed命令修改文件内容 salt '*' file.sed /etc/httpd/httpd.conf 'LogLevel warn' 'LogLevel info' # 在文件末尾添加一些内容 salt '*' file.append /etc/httpd/httd.conf 'maxclient 100' # 删除文件 salt '*' file.remove /tmp/foo
API调用就不说了,反正也是三个参数
● network模块
主要获取一些被控端的主机网络信息,不一条条写了,简单列举下:
network.ping 127.0.0.1 查看ping某个域名的情况
network.traceroute 127.0.0.1 查看traceroute到某个域名的情况
network.hwaddr eth0 查看eth0网卡的物理地址
network.in_subnet 10.0.0.0/16 查看被控端的ip是否在指定网段内
network.interfaces 查看网卡配置信息
network.ip_addrs 查看IP地址配置信息
network.subnets 查看子网信息
● pkg包管理模块
可以管理被控端的包管理工具比如yum,apt-get等,由于被控端有可能是不同发行版本的linux,这样通过这个命令就可以让salt来帮我们找到合适的包管理工具进行包的安装了。比如
pkg.install php
pkg.remove php
pkg.upgrade php
● service模块
进行被控端的service的管理
service.enable/disable 开启/关闭开机自启动
service.start/stop/restart/status 开启,关闭,重启,状态
service.reload 针对nginx的reload命令
除了以上这些模块,salt中还有各种其他模块可供使用,如果不满足还可以自定义一些Python模块来进行扩展,这个以后再说。
■ grains组件
上面提到过grains,是一个salt很重要的组件,它的基本作用是收集一些被控端的静态信息,然后根据这些信息来进行灵活的定制,使主控端可以对不同被控端进行不同的操作。grains数据的定义方式有两种,其一是在被控端定制配置文件,其二是通过主控端扩展模块API,后者的模块更加灵活,可以应用Python动态定义;前者则适合相对固定的键值对,下面举例说明:
● 被控端的grains数据
被控端的grains数据所在的位置由/etc/salt/minion决定,其中默认的参数default_include: minion.d/*.conf。然后比如我们创建了这么一个配置文件:【/etc/salt/minion.d/hostinfo.conf】,内容如下:
grains: roles: - webserver - memcache deployment: datacenter4 cabinet: 13
这是一个yml格式的文件,我们可以把它看成是一个类键值对格式的东西,grains是总起全文的,roles,depolyment,cabinet是三个key,每个key有值,如果值不止一个那么就用- 在key的名字前提示。
然后重启minion的服务,之后在主控端可以看到:
[root@localhost minion.d]# salt '*' grains.item roles deployment cabinet TestMinion: ---------- cabinet: 13 deployment: datacenter4 roles: - webserver - memcache
可以看到grains.item这个模块后面加上一些被控端的grain的key,可以显示出值。如果key是不存在的那么下面显示的值就会是空值而不是报错。这种在被控端配置grains数据就是有点被控端个性化配置的意思,比如不同的被控端可以将同一个key配置不同的值,然后在进行salt操作的时候利用-G这个参数来指定对不同配置值进行的不同操作。
比如 salt -G 'cabinet:13' cmd.run 'python -V' 或者 salt -C 'G@cabinet:13 and G@deployment:datacenter4' cmd.run 'python -V',就分别是在grains中cabinet是13(并且deployment是datacenter4)的被控端上执行python -V。
因为这部分grains数据是配置在被控端上的,而频繁地登录服务器修改文件来更改配置内容显然是不现实的,所以这部分grains其实还是比较适用于各个被控端虽有不同,但是不太会变动的那些配置。比如主机名等等。
● 主控端上配置grains
通过主控端进行grains的配置是更加灵活一点的做法,它允许你进行一些python的代码编写之后返回一些内容,从这些内容中获取信息并将其视为grains。比如按照下面这样操作:
首先在主控端的file_roots目录下(默认是/srv/salt)建立一个_grains目录,再到里面创建一个python脚本(可以叫任意名字,比如test.py)
#!/usr/bin/env python #coding=utf8 import subprocess def test(): grains = {} p = subprocess.Popen('python -V',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) out,err = p.communicate() grains['test_grain'] = out.strip() return grains
完成之后执行命令salt '*' saltutil.sync_all(此命令同步了很多东西,若只想同步grains可以sync_grains)把这个脚本同步到所有被控端的 /var/cache/salt/minion/extmods/grains和 /var/cache/salt/minion/files/base/_grains两个目录下。前者是最终的存放目录,而后者是临时存放位置。同时在前者目录中还会生成对应的python字节码文件即.pyc文件(这里就是test.pyc文件)
pyc文件生成之后就表名这个脚本中写入的grains信息已经被被控端接收了,此时就可以通过主控端salt '*' grains.item test_grain来查看值了。而这个值是通过在各个不同被控端上运行这个脚本获得而来的。
再来仔细看下这个脚本到底是怎么回事,其实其他不用看,主要看到grains这个字典,为这个字典添加一个键值对其实就是增加了一对grains的键值对。更神奇的是(虽然书上说不是),这个字典可以叫任意名字,也可以放到函数外面,然后假如有若干个函数中都有同一个名字的字典也不碍事,每个被加入字典的键值对都会反映到grain中去。。真的好自由啊。。(不过有一条,如果这个grains字典是一个函数内部的局部变量的话最后一定要返回,否则是不会反映到最终的grains结果中去的。)
具体salt是如何分析那个pyc文件,以及是如何提取出grain信息的,这个机理还有待研究。。
毋庸置疑的是,主控端的grains配置方法比被控端配置灵活很多且节省很多工作量。不过会花费一点传文件和通信的时间。
在网上看到了一段话不错:【在/srv/salt/_grains目录下的自定义脚本,在执行完成后,强烈建议移到一个bak目录下(这个bak不能在_grains里面,否则还是会被识别成生成grains的脚本)。使该目录下是没有文件,并且再执行一次salt '*' saltutil.sync_grains操作。使minion主机上自定义模块目录是干净的,这样做的好处是可以避免和另外两个文件有配置冲突,也避免脚本之间有重复的名字定义,造成取回的结果不对。】因为grains这个东西似乎是无限增量的。。不清楚到底要到哪里去删除一个grain。有grains.remove这个模块但是试了下并没有用,也就是说一次被写给一个被控端的grain就一直在那里了。。今天继续尝试了下,发现sync之后失效的grains没有消失是因为需要重启minion服务,而新增grains是不需要重启服务的。继续填坑。。再来补充一点,就是这个增删是走文件层面的,假如把脚本从这个目录下删除或者移走,那么经过一次sync之后文件中所有grains都会被删除,但是仅仅对脚本做一些改动,sync之后不会删除删掉grains,此时应重启salt-minion。
grains除了item还有items(查看所有键值对),ls(查看所有键)等命令,而sys.doc grains可以查看grains的所有命令用法,这个sys.doc也适用于其他模块。
grains还有一个grains.append,也可通过grains.append key 'value' 这个命令行的方式来添加某被控端的grains。此时添加的grains是被写入到/etc/salt/grains文件中的。关于通过grains.append方法添加的grains,再sync之后就会同步到位,不需要重启服务。
还有一个小问题,假如通过不同手段给同一个被控端增加了同名的grain可以吗?最终取值以谁为准?从网上资料来看,被控端minion上的/etc/salt/minion.d/*.conf优先级最高,其次是/etc/salt/grains,再次是通过主控端脚本分发的grain内容,最后是系统自带的一些grain。优先级高的含义是同名的grain取优先级最高的那个来源的值。
● grains的使用
上面对于grains的使用,只有1. 通过grains.xxx模块来查看grains的值,这个在实际应用salt过程中没有实质性作用。2. 通过-G参数来进行基于grains的分组。前者谈不上使用,后者其实也不是grians设计的主要目的(毕竟分组也可以通过主控端配置来实现),实际上我们可以结合模板语言比如jinja把grains整合到配置文件中,这样只要写好适当的配置文件,结合grains信息,每个被控端就可以得到个性化的配置了。具体怎么用先不说吧。。因为要牵扯到pillar等其他组件的。先往下看↓
总的来说,grains还是属于一个比较适合静态信息的方法。因为即便是主控端通过脚本进行配置这种比较灵活的方法也还是需要saltutil.sync_all一下。若遵循上面的建议还要删脚本干嘛的很麻烦。如果需要配置一些频繁变动的动态信息,还是要另寻他路。说到底主控端脚本数据填充只是一种快速填充数据的方法而不是频繁变更数据的采集方法。
■ pillar组件
pillar组件是salt中另一个比较重要的组件。和grains类似,它也是定义与被控端相关的一些数据,与grains不同的是,它可以修改被控端的信息,在主控端修改一些文件内容就能实现不同被控端只能访问一些各自的信息的管理。而且通过pillar定义的数据可以被其他组件比如state,API等使用。
定义pillar数据相比grains要更加复杂一些,比如首先我们需要在/etc/salt/master中配置好pillar_roots,默认是/srv/pillar。在其中创建一个top.sls的文件。这个文件名是规定的,被称为入口文件。入口文件的作用是规定哪些被控端被哪些文件影响,格式是YAML。比如:
base: '*': - data
其中base是根节点,'*'代表所有被控端都有的pillar数据,这些数据被定义在了data里,data其实是指同目录下的data.sls文件,这也是需要我们来创建并且填充内容的。像这样粗暴的使用'*'的话和直接用grains没有什么差别。这里只是做个示例。
随后再看/srv/pillar/data.sls这个文件:
appname: website flow: maxconn: 30000 maxmem: 6G
这个YAML格式规定的数据就是pillar数据本身了,此时输入命令salt '*' pillar.data就可以看到被控端都被约束了哪些pillar数据了。
● pillar使用
首先和grains类似的,pillar可以通过salt -I的办法来实现基于pillar数据的分组。当然这也不是主要功能。
第二个就是pillar可以基于grains的数据和jinja语法进行进一步的数据细化。把'*'分写成'id_a'和'id_b'两块内容而两者pillar数据又差得不多的话,写成两个data1.sls和data2.sls文件就很麻烦了。可以通过jinja语法来实现一块搞定:
appname: website flow: maxconn: 30000 maxmem: 6G {% if grains['id'] == 'TestMinion' %} maxcpu: 8 {% else %} maxcpu: 4 {% endif %}
请注意:maxcpu项不能因为上下有if/else结构就向内缩进,这样就破坏了YAML格式,导致无法读取数据。一定要和上面两项maxmem,maxconn对齐写
通过jinja的语法结合grains的数据,直接在data.sls一个文件里面就解决了两个不同的cpu个数配置。如果此时salt '*' pillar.data看下的话就可以看到TestMinion的maxcpu是8,其他的都是4。
■ state组件
MD没听说过还有个state啊。。好吧,据说state才是salt最NB的组件。。
通过state组件,可以进行基于sls文件(salt state)对被控端进行状态管理、支持包括程序包、文件、网络配置、系统服务、系统用户等。
state组件的整体架构和pillar有些类似,其入口文件也是top.sls,不过这个top.sls是放在file_roots下面的,默认就是/srv/salt/top.sls。和pillar的top.sls类似,也是支持jinja语言,grains以及pillar的数据引用。定义完成之后,和pillar不同的是需要执行salt '*' state.highstate才能让数据生效。下面按照书上给的例子进行state的说明。
这个例子中我们将先建立一些pillar数据,然后让state来引用这些pillar数据,形成有层级的数据引用模型。
首先在/srv/pillar下建立top.sls:
base: '*': - apache
上面讲pillar的时候没有提到的一点是这里引用apache其实除了直接引用同目录下的apache.sls文件之外还可以在同目录下创建apache子目录,然后在子目录中创建init.sls文件,在top.sls中引用apache相当于引用apache子目录的init.sls文件(有点像python的模块的引用)。所以我们创建/srv/pillar/apache目录,然后在其中建立init.sls文件
pkgs: {% if grains['os_family'] == 'Debian' %} apache: apache2 {% elif grains['os_family'] == 'RedHat' %} apache: httpd {% else %} apache: apache {% endif %}
完成之后运行命令salt '*' pillar.data pkgs应该就能看到结果了。
然后在创建/srv/salt/top.sls,进行state的配置:
base: '*": - apache
和上面进行pillar配置类似的,在/srv/salt下也建立apache子目录然后在其中创建init.sls文件:
apache: pkg: - installed - name: {{ pillar['pkgs']['apache'] }} service.running: - name: {{ pillar['pkgs']['apache'] }} - require: - pkg: {{ pillar['pkgs']['apache'] }}
这里的pkg,service.running包括下面的name,require等等字段名,都是有规定的不是自己瞎写的。而apache是自定义的,不过为了看起来方便,一般都取比较显而易见的名字。比如这个init.sls是管理apache的所以就取名叫apache。
运行命令salt '*' state.highstate进行运作,先说下效果吧,就是检查目标被控端是否安装有pillar中定义的apache字段的值,若没有装则通过包管理工具装之,若已经安装则开启这个服务。命令的返回是:
TestMinion: ---------- ID: apache Function: pkg.installed Name: httpd Result: True Comment: Package httpd is already installed. Started: 10:40:13.698974 Duration: 3005.802 ms Changes: ---------- ID: apache Function: service.running Name: httpd Result: True Comment: Started Service httpd Started: 10:40:16.707783 Duration: 566.182 ms Changes: ---------- httpd: True Summary ------------ Succeeded: 2 (changed=1) Failed: 0 ------------ Total states run: 2
可以看出,命令读取了init.sls之后主要进行了两个阶段的操作,针对的分别是pkg和service两种对象类型。
● 实践,基于salt进行nginx的配置集中化管理
这个实例是抄书上的,结合了salt的grains,pillar,state,jinja等组件,可以说是比较完整的一个实例了。
基本配置(/etc/salt/master)和上面提到过的差不多就不说了,而在/srv/salt/_grains下我们编写了一个python脚本,它为我们收集了各个被控端上的一个叫做max_open_file的grain,此grain的值是通过运行命令ulimit -n得来的,默认值是65535。再salt '*' sync_all之后我们可以先grains.item max_open_file确认下是否所有被控端都已经成功有了这个grain
接下来我们要配置pillar数据。之前pillar的top.sls我们都是用了最傻逼的'*'方式来匹配全部的被控端,实际上我们还可以用正则表达式来匹配若干主机,另外还可以这么写:
base: 'os:Debian': - match: grain - servers web1group: - match: nodegroup - web1server web2group: - match: nodegroup - web2server
下面的match字段指出了我们以什么依据进行的匹配,比如match: grain就是指上面的os:Debian是指grains['os'] == 'Debian',match: web1group就是指在master配置文件中配置的nodegroup是web1group的组。
上面的os:Debian是为了体现匹配规则的多样性加上去的,和下面的例子没有直接关系,可以忽视。web1server和web2server就像是之前的apache/init.sls一样,指代两个sls文件。为了方便起见我们这里没有使用子目录下init.sls的模式,而是直接在top.sls同目录下创建web1server.sls和web2server.sls。两个文件内容分别是:
【/srv/pillar/web1server.sls】 nginx: root: /www 【/srv/pillar/web2server.sls】 nginx: root: /data
结合master中配置的nodegroup,我们可salt '*' pillar.data nginx来查看不同主机上配置的root项的值了。
接下来要配置state了。首先是/srv/salt/top.sls:
base: '*': - nginx
同样方便起见,我们采用同目录下同名文件的形式引用nginx.sls。(这里顺便提一下,除了单个的引用,似乎还可以有subdir.nginx这种引用方法,此时nginx.sls文件是在同目录的子目录subdir下且不是init.sls)于是创建/srv/salt/nginx.sls,然后内容:
nginx: pkg: - installed file.manage: - source: salt://files/nginx/nginx.conf - name: /etc/nginx/nginx.conf - user: root - group: root - mode: 644 - template: jinja service.running: - enable: True - reload: True - watch: - file: /etc/nginx/nginx.conf - pkg: nginx
这个nginx.sls比上面说明state时使用的apache/init.sls文件相比多出了一个file.manage部分。这个模块是用来进行配置文件的管理的。source字段指出了相对于salt的file_roots下面的一个模板文件。template指出了这个模板文件采用什么语法,其余都一目了然,说到这里其实大家都应该看明白了。所谓的file.managed模块就是很贴心的一个配置文件具象化并上传(覆盖)的模块。files/nginx/nginx.conf可以是一个配置文件的模板文件,模板文件中基于jinja语法取pillar和grains的值来做一些逻辑运算,然后传送到被控端的指定位置。
同时这个模块返回的内容十分丰富,表明其作用远远不止替换变量和传文件那么简单。对于之前不存在的一个配置文件,它的返回像是这样:
TestMinion: ---------- ID: nginx Function: file.managed Name: /tmp/some-config.xml Result: True Comment: File /tmp/some-config.xml updated Started: 20:19:21.797316 Duration: 18.551 ms Changes: ---------- diff: New file mode: 0604 Summary ------------ Succeeded: 1 (changed=1) Failed: 0 ------------ Total states run: 1
提示了New file,以及其他各种各样的内容
如果是覆盖,那么它会提示哪些内容作了变动:
TestMinion: ---------- ID: nginx Function: file.managed Name: /tmp/some-config.xml Result: True Comment: File /tmp/some-config.xml updated Started: 20:23:25.236856 Duration: 19.982 ms Changes: ---------- diff: --- +++ @@ -9,6 +9,6 @@ <rootpath>/hsdata/hips/webapp</rootpath> - <hasRoot>True</hasRoot> + <hasRoot>False</hasRoot> </system> Summary ------------ Succeeded: 1 (changed=1) Failed: 0 ------------ Total states run: 1
如果说本次highstate同步过去之后和之前的文件内容上没有发生任何变化也会提示:
TestMinion: ---------- ID: nginx Function: file.managed Name: /tmp/some-config.xml Result: True Comment: File /tmp/some-config.xml is in the correct state Started: 20:22:55.645816 Duration: 19.358 ms Changes: Summary ------------ Succeeded: 1 Failed: 0 ------------ Total states run: 1
总之,这个功能十分强大
好像在file.managed这方面深入太多了。。让我们跳出来,重新看看这个sls文件的结构。首先,最上面的nginx是一个ID,是可以自由取名的,不过应该尽量保持语义易懂且准确。
第二层级的节点(pkg,file.managed,service.running)我们姑且称之为函数(官方文档叫function),这些函数是salt自己定义好的,更多详细的用法可以参考官方文档https://docs.saltstack.com/en/latest/ref/states/all/。我们这里用到了三个最为常用的函数模块:
pkg 包管理函数,会根据系统类型自动选择合适的包管理工具,然后进行操作。典型的pkg操作有:
pkg.installed 确保软件包已安装,若没有安装则调用包管理工具进行安装
pkg.latest 确认软件包是否是最新的,如果不是则更新到最新
pkg.remove 确保软件包已卸载,如果还装着则自动卸载
pkg.purge 在remove的同时删除其配置文件
file 文件管理函数,主要操作有:
file.managed 保证文件存在且是对应的状态(这句话的语法比较微妙,保证文件存在且是对应的状态,表名文件如果不存在则要分发,状态不对那就修改状态等等,这些和之前实例说的一致。)
file.recurse 这个就更那啥点,进行目录的分发了
file.absent 保证文件不存在,如果存在则删除
service 服务管理函数,主要操作:
service.running 确保服务在运行,如果不在就通过系统的启动服务方式(service start、systemctl start等)
service.enabled/disabled 确保服务开机启动/不启动
service.dead 确保服务没有在运行
第三层级的一些姑且称之为字段和值吧,这些信息描述了各个函数模块的参数。其实这些字段也可以分成两类,一类是普通的信息字段,比如file.managed下面的source,name等字段。另一类是被称为requisites的信息。这部分信息直接关系到整个state的操作。比如require这个字段就是requisites,它指出了进行某个函数操作之前必须要满足一些条件;
watch是另一个比较有用的requisites。它监测了一个文件,当这个文件的内容发生改变时自动执行一些事情,常常和service.running等函数配合使用。【可惜的是摸索了半天没搞明白到底怎么玩。。官方文档也很乱看不下去。。这个问题先放一下把】
● 再理一理grains,pillar,state模块的区别和联系
首先grains是最轻量级的,它可以灵活地增减,不需要另写文件。同时由于其轻量级的特性,导致其组分能力有点弱(需要人工地编写脚本或者配置来实现,略显麻烦)。grains的数据可以通过grains.item等模块查看。
pillar需要编写入口文件top.sls且这个文件是在配置项pillar_roots下面的,此外pillar根据入口文件中定义的组分给适当的被控端关联适当的被引用文件。被引用文件有两种被引用形式(直接和子目录/init.sls)。pillar相关的文件中可以基于jinja语法对grains数据做出调用,也就是说pillar数据的定义可以基于grains数据。pillar数据可以通过pillar.data等模块查看。pillar虽然比grains要更加高级一点,但是究其本质仍然是一种辅助数据定义方式,是被动而非主动的。也因为其单纯定义数据的特点,其中大多数字段名之类的都没有关键字一说,用户可以自定义。
state和pillar一样需要定义入口文件和被引用文件,相比于pillar,state有了主动性(安装,启动,传配置等等),也有了更高级的语法(子模块都是被定义好名字的,用户只能调用而不能自定义名字)。而state又可以同时调用pillar和grains两者的数据,可以说是一个操作最终落地的地方。