puppet介绍
puppet是一个IT基础设施自动化管理工具,它能够帮助管理员管理基础设施的整个生命周期:供应 配置 联动(编排) 报告
支持的并发性很好,适用于企业级较大规模
puppet是用ruby语言写的 facter是puppet的一个模块
ansible靠模块,puppet靠资源;puppet的模块类似于ansible的角色。
定义模块的文件叫清单(manifest),主要是定义资源的叫资源清单。如果定义的清单是为了设定目标主机到底调用哪些模块的,叫做站点清单(site manifest)。
基本工作逻辑:
puppet是master-agent模型,master为了实现代码复用定义了很多模块。而后可以为每一个被管控主机定义一个站点清单的配置,说明这个主机要调用那些模块。接着,每一个agent主机每隔固定时长(通常默认为30分钟)到master端请求自己的相关配置。master端通过证书去识别客户端身份,获取其主机名称标识,从而在本地查找这个主机相关的配置,拿出来模块并发送给目标主机,从而实现管控。
它的好处在于,一旦我们定义好了puppet的master上的每一主机相关配置,那每一个主机放在那里你就不用管它了。他如果出了故障,最长30分钟以后能自己修复回来,只要是我们管控的目标范围内的。
官方网站:
https://puppet.com/products/open-source-projects
下载路径:
https://yum.puppet.com/
三层模型
Configuration Language配置语言
Transactionl Layer事物关系层
Resource Abstraction Layer资源抽象层
两种使用模型
单机使用:手动应用清单
puppet apply
master/agent:由agent周期性地向Master请求清单并自动应用于本地;
master:puppet,puppet-server
agent:puppet
安装:yum install puppet
agent端只需要安装puppet,master才需要安装puppet-server
主程序:/usr/bin/puppet
使用puppet help可以看到很多子命令
如果想看某一个子命令的帮助,也可以使用help查看:
子命令也有子命令
puppet资源
·资源抽象的纬度(RAL如何抽象资源的?):
类型:具有类似属性的组件,例如package、service、file;
将资源的属性或状态与其实现方式分离;
仅描述资源的目标状态,也即期望其实现的结果状态,而不是具体过程;
RAL由“类型”和提供者(provider);
·puppet describe:
Prints help about Puppet resource types, providers, and metaparameters.
基础使用语法:
puppet describe [-h|--help] [-s|--short] [-p|--providers] [-l|--list] [-m|--meta] [type]
-l:列出所有资源类型;
-s:显示指定类型的简要帮助信息;
-m:显示指定类型的元参数,一般与-s一同使用;
puppet describe group 参数描述
puppet describe -s group 简要描述
puppet describe -s group -m 多显示了元参数(描述属性)
常用元参数:before,require,notify,subscribe
-p:某种资源的具体实现方法
·资源定义:向资源类型的属性赋值来实现,可称为资源类型实例化;
定义了资源实例的文件即清单,manifest;
1、定义资源的语法:
Vim XXX.pp #建立一个后缀为pp的文件
type {'title':
attribute1 => value1,
atrribute2 => value2,
……
}
注意:type为资源类型,必须使用小写字符;title为自己起的名字,是一个字符串,在同一类型中必须惟一;每一个键值对用逗号隔开,最后一个键值对后面的逗号可有可无。
在puppet中定义的每一个资源都必须具有幂等性,意思就是执行一次操作与执行n次操作结果必须是一样的。例如有一个操作是创建文件,那么第一次操作会创建文件,第二次再执行时文件已经存在就不会创建文件了这样就不幂等,因此如果我们想让它幂等,就给它加一个条件:“如果文件不存在”就创建目录,这样无论执行多少次都是幂等的了。
2、资源属性中的三个特殊属性:
Namevar, 可简称为name;
ensure:资源的目标状态;
Provider:指明资源的管理接口;
3、资源清单文件使用:
使用puppet apply file运行,除此之外还有如下选项:
-h 帮助
-v 显示详细信息
-d 打开调试信息(会输出许多信息)
-e 直接执行命令
-l 指定日志文件,否则内容可能会直接输出到标准输出
-noop 使用'noop'模式,会测试的跑一遍而不真正执行
资源类型:
1、group
创建组
Manage groups.
属性:
name:组名;
gid:GID;
system:是否为系统组,true OR false;
ensure:目标状态,present/absent;
members:成员用户;
编辑文件:
如果name跟title一样的话可以省略,如果不一样就不能省略
执行:
创建成功:
2、user
创建用户
Manage users.
属性:
name:用户名;
uid: UID;
gid:基本组ID;
groups:附加组,不能包含基本组;
comment:注释信息;
expiry:过期时间;
home:家目录路径;
shell:默认shell类型;
system:是否为系统用户;
ensure:present/absent;
password:加密后的密码串;
(属性可以什么都不写,只要写上ensure=>present即可创建用户)
编辑文件:
执行:
创建成功:
再看一下mygrp组,user1已经作为附加组加入mygrp中了:
我们再来定义一个user,
当我们要指定多个附加组的时候格式如下:
依赖关系:
·资源引用:
Type['title']
类型的首字母必须大写!
·关系元参数:before/require两种方法都可以
A before B: B依赖于A,定义在A资源中;
{
...
before => Type['B'],
...
}
B require A: B依赖于A,定义在B资源中;
{
...
require => Type['A'],
...
}
如果定义一个用户指明的附加组是不存在的话,
当我们使用noop模式的时候不会报错:
但是当我们真正去执行的时候,就报错了:
因为noop只能检查语法错误,而这里是依赖的资源不存在,是资源环境的问题。
这里就提到了依赖关系,也就是说,我们必须先创建一个组,才能创建一个以那个组为附加组的用户。那我们现在就来创建一下,为了方便演示,我们就只创建一个组。
有两种方式,一种是在被依赖资源上用before:
另一种是在依赖的资源上用require:
这两种方法都可以,意思就是,先建立一个redhat组,然后再创建用户linux。
这回再运行,就没问题了:
3、package:
安装包
Manage packages.
属性:
ensure:
安装:installed,present, 'any version string(implies present)'
最新版本:latest
卸载:absent,purged
name:包名;
source:程序包来源,仅对不会自动下载相关程序包的provider有用,例如rpm或dpkg;如果是URL会自动下载到本地。
provider:指明安装方式;
platform:平台(i386,x86_64,)
编辑文件:
运行,安装完成:
注意,如果是指定rpm包直接在本地安装也是可以的,如果使用这种方式就是用本地的source直接安装的话,很有可能无法解决依赖关系。所以当我们使用source直接指定rpm包安装时,建议不要使用rpm作为provider,而是指明用yum安装,因为这样能帮我们解决依赖关系。
4、service:
关于服务的操作
Manage running services.
属性:
ensure:服务是否开启。有效值为`stopped` (或 `false`), `running` (或 called `true`).
name:控制服务名字
enable:是否设定服务为开机自启状态。 有效值为 `true`, `false`, `manual`.
path:查找init脚本的搜索路径。多值应该由逗号分隔或设置为数组。默认为/etc/init.d/;
binary:启动服务的二进制文件程序路径
hasrestart:是否支持restart。true表示支持,false表示不支持。
hasstatus:是否支持状态探测。
start:手动定义启动命令;
stop:手动定义停止命令;
status:手动定义探测服务状态命令。
restart:通常用于定义reload操作;
编辑文件:
运行,启动成功:
如果你定义一个service,但是那个程序包没有安装,是否能启动呢?那我们来试一下:
如图所见,果然是不行的
因此,service要依赖于对应程序的程序包,用我们刚才刚学过的依赖,定义一下:
安装成功:
并且也启动成功:
5、file:
文件
Manages files, including their content, ownership, and permissions.
ensure: `present`, `absent`,(建议直接给后面三个值:) `file`, `directory`, and `link`.
file:类型为普通文件,其内容由content属性生成或复制由source属性指向的文件路径来创建;
link:类型为符号链接文件,必须由target属性指明其链接的目标文件;
directory:类型为目录,可通过source指向的路径复制生成,recurse属性指明是否递归复制;
path:文件路径;(因为文件没有name属性,所以在定义的时候title部分写的就是path)
source:源文件;(如果是复制的话就用这个,如果是复制多个文件,那么path就要定义一个目录了)
content:文件内容;(如果是自定义的话就用这个)
recurse:递归
target:符号链接的目标文件;
owner:属主
group:属组
mode:权限;
atime/ctime/mtime:时间戳;
编辑文件:
那我们在文件里写了源地址,我们就要去创建它并且在里面放一个redis.conf文件供我们复制。
为了便于区分,我们稍微改一下这个redis.conf文件,监听本机所有地址。
运行成功:
查看一下文件,确实复制过去了:
刚才是复制一个文件,我们也可以自己制定内容去生成一个文件:
运行成功:
创建成功:
刚才两个都是生成文件,我们还可以生成一个目录:
只需要这么写,就可以生成一个目录
创建成功
可见,如果我们指定一个目录,但是又没有指定生成的方式,就会创建目录
我们再试着向目录里拷贝文件:
指定源,还要记得指定递归:
可以看到,它将文件一个一个复制过去了:
复制成功,文件夹下的3个文件都过去了:
它的复制机制是,先创建目录,然后将文件一个一个复制过去。
那我们把刚才创建的目录以及目录下的文件都删掉,再来创建一个.pp文件:
这次我们定义两个文件
我们运行之后发现它并没有生成目录,而是生成了名为test.dir的文件
再查看一下文件内容,发现它复制的是第一个文件的内容:
可见,puppet并不支持把文件复制成目录(本来也没办法复啊)。也就是说,即使你指定了ensure为directory类型,但是你的source却指定的是文件,那么它就会创建文件并复制内容。如果你的source指定了不止一个文件的话,那么它只会复制第一个文件的内容。
接下来,我们创建一个链接类型:
链接指向之前创建过的text.txt文件
运行成功:
创建链接成功:
练习:
学过了之前这几个资源,目前为止,我们可以完成一整套的操作了,那我们现在来定义一下试试:
看一下原本默认的/etc/redis.conf文件属性是这样的:
我们不能只定义资源,还得定义它们之间的依赖关系,service依赖于file,file依赖于package。
一共有几种不同的方式定义:
方法一:
方法二:
方法三:
但是呢,我们如果修改了配置文件之后需要重启,目前这个脚本不会重启,因为它发现服务启动了之后就不会执行启动操作了,因此我们要加入下一个关系,叫通知关系。
通知关系:通知相关的其它资源进行“刷新”操作;
notify
A notify B:B依赖于A,且A发生改变后会通知B;
{
...
notify => Type['B'],
...
}
subscribe
B subscribe A:B依赖于A,且B监控A资源的变化产生的事件;
{
...
subscribe => Type['A'],
...
}
因此我们应该定义,如果配置文件修改了,就通知service重启服务:
方法一:
方法二,可以用波浪线:
方法三:
这样就可以在修改完配置文件之后重启服务了。
6、exec:
自定义命令。注意,命令必须是幂等的。
**command** (*namevar*):要运行的命令;
cwd:The directory from which to run the command.
**creates**:文件路径,仅此路径表示的文件不存在时,command方才执行;
user/group:运行命令的用户身份;
path:用于命令执行的搜索路径。如果没有指定路径,命令必须完全限定。有两种方式定义path
path => '/bin:/sbin:/usr/bin:/usr/sbin' 用冒号隔开
path => ['/bin','/sbin','/usr/bin','/usr/sbin'] 用中括号并用逗号隔开
onlyif:此属性指定一个命令,此命令正常(退出码为0)运行时,当前command才会运行;
unless:此属性指定一个命令,此命令非正常(退出码为非0)运行时,当前command才会运行;
refresh:重新执行当前command的替代命令;
refreshonly:仅接收到订阅的资源的通知时方才运行;
编辑文件:
先不指定幂等关系,只是单纯的创建个文件:
第一次运行,成功:
创建成功
那我们现在再来运行一遍:
就报错了,因为文件已经存在了。
因此我们应该稍微修改一下文件,给它加一个限定条件,如果文件不存在就创建:
这样无论运行多少遍都没问题了:
除了用那种命令的条件,还可以用creates属性:
仅此路径表示的文件不存在时,command方才执行。
我们还可以使用onlyif或者unless:
可以看到,当一个用户存在的时候,是可以执行id+用户名这个命令的,如果用户不存在就不能执行:
因此,我们可以使用这一点,编写一个文件:
这样就是如果用户不存在就创建,存在就不执行。
7、cron
计划任务
command:要执行的任务;
ensure:present/absent;
定义时间:
hour:
minute:
monthday:
month:
weekday:
user:以哪个用户的身份运行命令
target:添加为哪个用户的任务
name:cron job的名称;
编辑文件:
运行成功:
计划任务添加成功:
8、notify
向屏幕显示一段话
属性:
message:信息内容
name:信息名称;
编辑文件:
运行,显示成功:
9、非核心类型常用资源:
yumrepo
host
tag
可以为资源定义tag,格式:
type{'title':
...
tag => 'TAG1',
}
type{'title':
...
tag => ['TAG1','TAG2',...],
}
手动调用tag:
puppet apply --tags TAG1,TAG2,... FILE.PP
编辑文件:
我们定义一个tag,让调用tag的时候就会只执行file的部分,然后我们在file部分里面又定义了notify触发service部分:
使用--tags调用我们定义的tag(这样的话就不会再运行package):
这种标签定义的意义可以用于修改了配置文件之后使用,就会触发更新配置文件和重新启动了。
变量
·variable定义规则:
$variable_name=value (定义变量的时候也要加$符号)
数据类型:
字符型:引号可有可无;但单引号为强引用,双引号为弱引用;
数值型:默认均识别为字符串,仅在数值上下文才以数值对待;
数组:[]中以逗号分隔元素列表;
布尔型值:true, false;
hash:{}中以逗号分隔k/v数据列表;键为字符型,值为任意puppet支持的类型;{ 'mon' => 'Monday', 'tue' => 'Tuesday', };
undef:未定义 ;
正则表达式:
(?
(?-
OPTIONS:
i:忽略字符大小写;
m:把.当换行符;
x:忽略
我们一般用这种格式:(?i-mx:PATTERN)意思是忽略字符大小写,不把.当换行符,不忽略空白字符
注意:正则表达式不能赋值给变量 ,仅能用在接受=~或!~操作符的位置;
·puppet的变量种类:
1.facts 系统级的环境变量:
由facter提供;top scope;可以使用facter -p查看
(如果是apply的话,它在运行的时候会先收集本机上的所有facts变量。
如果是master/agent的话,agent会在请求之前先向master发送自己的主机名和facts,然后在请求自己相关的配置。)
使用facter -p查看里面有这么几个变量
我们可以使用它:
运行一下,显示成功:
2.内建变量:
master端变量
$servername 服务器名称
$serverip 服务器IP
$serverversion 服务器版本
agent端变量
$clientcert 证书
$clientversion 客户端版本
$environment 客户端所处环境
parser变量
$module_name
3.用户自定义变量:
·变量作用域,称为Scope;
Scope是一个特定的代码区域,用于同程序中的其他代码隔离开来。
在puppet中,scope可用于限定变量及资源默认属性的作用范围,但不能用于限定资源名称及资源引用的生效范围。
顶级作用域top scope: ($::var_name调用全局变量)
节点作用域node scope
类作用域class scope
在顶级作用域上定义的变量对所有节点和类都有效,在节点上定义的作用域只在当前节点上有效。查找的时候从内到外从小到大查找,最内层的优先级最高。
如果想跨类调用,可以使用变量的完全限定名称。
操作符
比较操作符 布尔操作符 算术操作符
<< 左移位(相当于成以10);>>右移位(相当于除以10)
<< 左移位(相当于成以10);>>右移位(相当于除以10)
流程控制
puppet流程控制语句:
1、if语句:
CONDITION的给定方式:
(1) 变量
(2) 比较表达式
(3) 有返回值的函数
例子1:
如果版本号是7就安装mariadb包,否则就安装mysql包
例子2:
2、case语句:
CONTROL_EXPRESSION:
(1) 变量
(2) 表达式
(3) 有返回值的函数
各case的给定方式:
(1) 直接字串;
(2) 变量
(3) 有返回值的函数
(4) 正则表达式模式;
(5) default
将刚才的if的例子2改成case方式,如下:
3、selector语句:
selector与case类似,只不过就是,case是满足条件执行代码,而selector是满足条件返回一个值。
CONTROL_VARIABLE的给定方法:
(1) 变量
(2) 有返回值的函数
各case的给定方式:
(1) 直接字串;
(2) 变量
(3) 有返回值的函数
(4) 正则表达式模式;
(5) default
注意:不能使用列表格式;但可以是其它的selecor;
因此,其实刚才的例子当中,用selector要更方便:
用这种方式,也能实现同样的功能。