- Puppet的基本概念
- Puppet的资源
- Puppet的资源类型
- Puppet的变量
- Puppet的流程控制语句
- Puppet的类
- Puppet的模板
一、Puppet的基本概念
(一)puppet的工作模型:
- 单机模型(standalone):手动应用清单
- master/agent:由agent周期性地向Master请求清单并自动应用于本地
(二)单机模型的程序环境:
- 配置文件:/etc/puppet/puppet.conf
- 主程序:/usr/bin/puppet
(三)puppet基本语法:
Usage: puppet <subcommand> [options] <action> [options]
子命令的选项
help:帮助
apply:本机执行清单
describe:显示指定资源类型的帮助
agent:puppet的agent端守护进程
master:puppet的master端守护进程
module:在Puppet Forge上建立、安装、搜索模块puppet apply的语法:
puppet apply [-d|--debug] [-v|--verbose] [-e|--execute] [--noop] <file>
二、Puppet的资源
(一)资源抽象的维度:
- 类型:具有类似属性的组件,例如package、service、file
- 将资源的属性或状态与其实现方式分离
- 仅描述资源的目标状态,也即期望其实现的结果状态,而不是具体过程
(二)puppet describe的语法:
- puppet describe [-h|--help] [-s|--short] [-p|--providers] [-l|--list] [-m|--meta] [type]
-l:列出所有资源类型
-s:显示指定类型的简要帮助信息
-m:显示指定类型的元参数,一般与-s一同使用
(三)资源定义
资源定义:通过对资源类型的属性赋值来实现,可称为资源类型实例化
定义了资源实例的文件即清单,manifest
-
定义资源的语法:
type {'title': attribute1 => value1, atrribute2 => value2, …… }
type必须使用小写字符;title是一个字符串,在同一类型中必须惟一
资源属性中的三个特殊属性:
namevar: 可简称为name,省略时将由title表示
ensure:资源的目标状态
Provider:指明资源的管理接口
三、Puppet的资源类型
(一)group:管理用户组
属性:
name:组名
gid:GID
system:是否为系统组,true/false
ensure:目标状态,present/absent
members:成员用户-
实验1: 建立用户组:组名mygrp,属于系统用户组
vim group.pp group {'mygrp': ensure => present, name => mygrp, system => true, } puppet apply -v --noop group.pp // 干跑,测试而不真正执行 puppet apply -v group.pp //真正执行 grep 'mygrp' /etc/group
(二)user:管理用户
属性:
name:用户名
uid: UID
gid:基本组ID
groups:附加组,不能包含基本组
comment:注释
expiry:过期时间
home:家目录
shell:默认shell类型
system:是否为系统用户
ensure:present/absent
password:加密后的密码串资源引用:
Type['title']
注意:类型的首字母必须大写-
依赖关系元参数:before/require
-
A before B:B依赖于A,定义在A资源中
{ ... before => Type['B'], ... }
-
B require A:B依赖于A,定义在B资源中
{ ... require => Type['A'], ... }
-
-
实验2:建立用户:用户名user2,uid=3000,shell为tcsh,家目录为/tmp/user2,附属组为mygrp, testgrp
注意:
(1)必须在清单中包含附属组mygrp, testgrp的定义
(2)user2资源的建立依赖于mygrp, testgrp组的建立,故需要设置关系元参数
(3)可以解除require语句的注释,但需要同时将testgrp, mygrp资源定义中的before语句注释,即before与require不能重复定义相同的关系group {'testgrp': ensure => present, before => User['myuser'] } user{ 'myuser': ensure => present, name => 'user2', uid => 3000, shell => '/bin/tcsh', home => '/tmp/user2', groups => [mygrp,testgrp], # require => [Group['mygrp'],Group['testgrp']], } group {'mygrp': ensure => present, name => mygrp, system => true, before => User['myuser'], }
(三)package:管理软件包
属性:
ensure:installed, present, latest, absent
name:包名
source:程序包来源,仅对不会自动下载相关程序包的provider有用,例如rpm或dpkg
provider:指明安装方式-
实验3:安装redis软件包
注意:资源定义中没有name属性,则name为资源名称vim packages.pp package{'redis': ensure => latest, } puppet apply -v --noop packages.pp puppet apply -v packages.pp rpm -q redis
(四)service:管理服务
属性:
ensure:stopped/running, false/true
enable:是否自启动,true, false, manual
name:
path:脚本的搜索路径,默认为/etc/init.d/
hasrestart:是否有重启功能
hasstatus:是否有查询状态功能
start:定义启动操作
stop:定义停止操作
status:定义查询状态操作
restart:通常用于定义reload操作-
实验4:启动redis服务
vim service.pp service{'redis': ensure => running, enable => false, hasrestart => true, restart => 'systemctl restart redis.service', require => Package['redis'], } package{'redis': ensure => latest, } puppet apply -v --noop service.pp puppet apply -v service.pp systemtctl start redis
(五)file:管理文件
属性:
ensure:present, absent, file, directory, link
(1)file:类型为普通文件,其内容由content属性生成或复制由source属性指向的文件路径来创建
(2)link:类型为符号链接文件,必须由target属性指明其链接的目标文件
(3)directory:类型为目录,可通过source指向的路径复制生成,recurse属性指明是否递归复制
path:文件路径
source:源文件
content:文件内容
target:符号链接的目标文件
owner:属主
group:属组
mode:权限
atime/ctime/mtime:时间戳-
通知关系:通知相关的其它资源进行“刷新”操作,包含notify/subscribe
- A notify B:B依赖于A,且A发生改变后会通知B
{ ... notify => Type['B'], ... }
- B subscribe A:B依赖于A,且B监控A资源的变化产生的事件
{ ... subscribe => Type['A'], ... }
-
实验5:文件管理相关操作
- 实验5-1:复制文件至文件
vim file.pp file{'/etc/redis.conf': ensure => file, source => '/root/redis.conf', owner => 'redis', group => 'root', mode => '0644', } puppet apply -v --noop file.pp puppet apply -v file.pp cat /etc/redis.conf
- 实验5-2:复制目录至目录
vim directory.pp file{'yum.repos.d': ensure => directory, path => '/tmp/yum.repos.d/', source => '/etc/yum.repos.d', recurse => true, } puppet apply -v --noop directory.pp puppet apply -v directory.pp tree /tmp/yum.repos.d/
- 实验5-3:建立软链接
vim link.pp file{'redis.conf': ensure => link, path => '/tmp/redis.conf', target => '/etc/redis.conf', } puppet apply -v --noop link.pp puppet apply -v link.pp ll /tmp/redis.conf
- 实验5-4:复制文件后出发相关服务重启
vim redis.pp package{'redis': ensure => installed, name => redis, } file{'redis.conf': ensure => file, path => '/etc/redis.conf', owner => 'redis', group => 'root', source => '/root/redis.conf', mode => '0644', require => Package['redis'], notify => Service['redis'], // 与后面的subscribe语句表达相同逻辑时只能二选一 } service{'redis': ensure => running, name => 'redis', hasrestart => true, restart => 'systemctl restart redis.service', # subscribe => File['redis.conf'], } puppet apply -v --noop redis.pp puppet apply -v redis.pp
修改/root/redis.conf文件内容后,重新执行puppet apply,可以看到配置文件变动触发了服务的刷新
puppet apply -v redis.pp
通知关系和依赖关系的简化写法1:当资源定义的顺序与关系顺序相同时采用
vim redis.pp package{'redis': ensure => installed, name => redis, }-> // 名为redis的package资源被名为redis.conf的file资源所依赖 file{'redis.conf': ensure => file, path => '/etc/redis.conf', owner => 'redis', group => 'root', source => '/root/redis.conf', mode => '0644', # require => Package['redis'], # notify => Service['redis'], }~> // 名为redis.conf的file资源发生变化需通知名为redis的service资源 service{'redis': ensure => running, name => 'redis', hasrestart => true, restart => 'systemctl restart redis.service', # subscribe => File['redis.conf'], }
通知关系和依赖关系的简化写法2:当资源定义的顺序与关系顺序不同时采用
vim redis.pp package{'redis': ensure => installed, name => redis, } file{'redis.conf': ensure => file, path => '/etc/redis.conf', owner => 'redis', group => 'root', source => '/root/redis.conf', mode => '0644', # require => Package['redis'], # notify => Service['redis'], } service{'redis': ensure => running, name => 'redis', hasrestart => true, restart => 'systemctl restart redis.service', # subscribe => File['redis.conf'], } Package['redis']->File['redis.conf']~>Service['redis']
(六)exec:执行命令
注意:执行的命令必须是幂等的
属性
command:要运行的命令
creates:文件路径,仅此路径表示的文件不存在时,command方才执行
user/group:运行命令的用户身份
path:运行命令的搜索路径,若没有指明,则命令必须路径完全
onlyif:指定命令正常(退出码为0)运行时,当前command才会运行
unless:指定命令非正常(退出码为非0)运行时,当前command才会运行
refresh:重新执行当前command的替代命令
refreshonly:仅接收到订阅的资源的通知时方才运行-
实验6:exec资源定义示例
vim exec1.pp // 建立目录 exec{'makedir': command => 'mkdir /tmp/testdir', path => '/bin:/sbin:/usr/bin:/usr/sbin', creates => '/tmp/testdir', // 根据文件存在判断实现幂等性 } vim exec2.pp exec{'createuser': command => 'useradd user3', path => '/bin:/sbin:/usr/bin:/usr/sbin', unless => 'id user3', // 根据命令执行结果判断实现幂等性 } vim exec3.pp exec{'installpkg': command => 'yum -y install zabbix-agent zabbix-sender', path => '/bin:/sbin:/usr/bin:/usr/sbin', onlyif => 'yum repolist | grep -i "zabbix"', }
执行exec.pp
执行exec2.pp
再次执行exec2.pp,由于user3已经存在,id user3命令的返回值为0,故不执行命令
执行exec3.pp,由于zabbix相关仓库,故命令不予执行
(七)cron:周期性任务计划
属性:
command:要执行的任务
ensure:present/absent
hour:
minute:
monthday:
month:
weekday:
user:以哪个用户的身份运行命令
target:添加为哪个用户的任务
name:cron job的名称-
实验7:周期任务计划的定义,每5分钟向172.18.0.1同步时间
cron{'timesync': command => '/usr/sbin/ntpdate 172.18.0.1 &> /dev/null', ensure => present, user => 'root', minute => '*/5', }
(八)notify:发送消息
单机模式下显示在标准输出上,master/agent模式下记录在agent的日志中
属性:
message:信息内容
name:信息名称-
实验8:发送一条消息
notify{'helloworld': message => 'hello world!', }
四、Puppet的变量
定义变量格式:$variable_name=value
引用变量格式:$variable_name,注意仍旧有$
数据类型:
字符型:引号可有可无;但单引号为强引用,双引号为弱引用
数值型:默认均识别为字符串,仅在数值上下文才以数值对待
数组:[]中以逗号分隔元素列表
布尔型值:true, false
hash:{}中以逗号分隔k/v数据列表; 键为字符型,值为任意puppet支持的类型
e.g. { 'mon' => 'Monday', 'tue' => 'Tuesday', }
undef:未定义-
正则表达式
格式:
(?<ENABLED OPTION>:<PATTERN>)
(?-<DISABLED OPTION>:<PATTERN>)OPTIONS:
i:忽略字符大小写
m:把.当换行符
x:忽略<PATTERN>中的空白字符
e.g. (?i-mx:PATTERN):忽略字母大小写,但不把.当换行符、不忽略<PATTERN>中的空白字符注意:不能赋值给变量 ,仅能用在接受
=~
或!~
操作符的位置
-
puppet的变量种类:
- facts:由facter提供,
facter -p
命令查询 - 内建变量:
master端变量:$servername, $serverip, $serverversion
agent端变量:$clientcert, $clientversion, $environment
parser变量:$module_name - 用户自定义变量:
- facts:由facter提供,
-
实验9:变量的定义和引用举例
vim installpkg.pp $pkgname = 'nginx' package{'installpkg': name => "$pkgname", ensure => latest, } puppet apply -v --noop installpkg.pp puppet apply -v installpkg.pp
五、Puppet的流程控制语句
(一)if语句:
-
语法
// 双分支 if CONDITION { ... } else { ... } // 多分支 if CONDITION { ... } elsif CONDITION { //注意拼写"elsif" ... } else { ... }
CONDITION的给定方式:
变量
比较表达式
有返回值的函数-
实验10:if语句使用举例,根据linux的不同版本安装不同的apache软件包
vim if.pp if $osfamily == 'Debian' { $apache_name = 'apache2' } else { $apache_name = 'httpd' } package{"apache_install": name => "$apache_name", ensure => latest, } puppet apply -v --noop if.pp rpm -q httpd puppet apply -v if.pp rpm -q httpd
(二)case语句:
-
语法
case CONTROL_EXPRESSION { case1: { ... } case2: { ... } case3: { ... } ... default: { ... } }
CONTROL_EXPRESSION的给定方式
变量
表达式
有返回值的函数各case的给定方式:
直接字串
变量
有返回值的函数
正则表达式模式
default-
实验11:case语句使用举例,根据linux的不同版本安装不同的apache软件包
vim case.pp case $osfamily { "Debian":{ $apache_name = "apache2" } /(?i-mx:Redhat)/:{ $apache_name = "httpd" } default:{ $apache_name = "apache" } } package{"apache_install": name => "$apache_name", ensure => latest, } puppet apply -v --noop case.pp rpm -q httpd puppet apply -v case.pp rpm -q httpd
(三)selector语句:
-
语法
CONTROL_VARIABLE ? { case1 => value1, case2 => value2, ... default => valueN, }
CONTROL_VARIABLE的给定方法:
变量
有返回值的函数各case的给定方式:
直接字串
变量
有返回值的函数
正则表达式模式
default-
实验12:selector语句使用举例,根据linux的不同版本安装不同的apache软件包
vim selector.pp $apache_name = $osfamily ? { "Debian" => "apache2", /(?i-mx:Redhat)/ => "httpd", default => "apache", } package{"apache_install": name => "$apache_name", ensure => latest, } puppet apply -v --noop selector.pp rpm -q httpd puppet apply -v selector.pp rpm -q httpd
六、Puppet的类
类:puppet中命名的代码模块,常用于定义一组通用目标的资源,可在puppet全局调用
类可以被继承,也可以包含子类-
语法格式:
class NAME { ...puppet code... } // 类可以定义传入参数 class NAME(parameter1, parameter2) { ...puppet code... }
-
类代码只有声明后才会执行,调用方式:
当类无参数时:
include CLASS_NAME1, CLASS_NAME2, ...
当类有参数时:class{'CLASS_NAME': attribute => value, }
-
类的继承:继承的子类可以在父类的基础上新增属性
class PARENT_CLASS_NAME::SUB_CLASS_NAME inherits PARENT_CLASS_NAME { // 完全限定名称(FQN):父类名::子类名 ...puppet code... }
在子类中为父类的资源新增属性或覆盖指定的属性的值
Type['title'] { attribute1 => value, ... }
在子类中为父类的资源的某属性增加新值
Type['title'] { attribute1 +> value, ... }
-
实验13:类的应用举例
- 实验13-1:无参数类的定义和调用
vim class1.pp class instwebsrv { $apache_name = $osfamily ? { "Debian" => "apache2", /(?i-mx:Redhat)/ => "httpd", default => "apache", } package{"apache_install": name => "$apache_name", ensure => latest, } } include instwebsr puppet apply -v --noop class.pp rpm -q httpd puppet apply -v class.pp rpm -q httpd
- 实验13-2:有参数类的定义和调用
vim class2.pp class dbserver($pkg='mariadb-server',$srv='mariadb.service') { package{"$pkg": ensure => latest } service{"$srv": ensure => running, enable => true, } } if $operatingsystem == "CentOS" or $operatingsystem == "RedHat" { case $operatingsystemmajrelease { '7':{ $pkg_name='mariadb-server' $srv_name = 'mariadb.service'} default:{ $pkg_name = 'mysql-server' $srv_name= 'mysqld.service'} } } class {'dbserver': pkg => "$pkg_name", srv => "$srv_name", } rpm -q mariadb-server ss -ntl | grep 3306 puppet apply -v class2.pp rpm -q mariadb-server ss -ntl | grep 3306
- 实验13-3:继承子类的定义和调用,实现redis的主从配置
// 在主从主机分别执行如下操作 mkdir redis.module cd redis.module/ cp /etc/redis.conf ./redis-master.conf cp /etc/redis.conf ./redis-slave.conf vim redis-master.conf bind 0.0.0.0 vim redis-slave.conf bind 0.0.0.0 slaveof 192.168.136.230 6379 // 主节点的ip vim redis.pp class redis { package{'redis': ensure => latest, } -> service{'redis': ensure => running, enable => true, hasrestart => true, restart => 'service redis restart', } } class redis::master inherits redis { file{'/etc/redis.conf': ensure => file, source => '/root/redis.module/redis-master.conf', owner => redis, group => root, require => Package['redis'], } Service['redis'] { restart => 'systemctl restart redis.service', subscribe => File['/etc/redis.conf'], } } class redis::slave inherits redis { file{'/etc/redis.conf': ensure => file, source => '/root/redis.module/redis-slave.conf', owner => redis, group => root, require => Package['redis'], } Service['redis'] { restart => 'systemctl restart redis.service', subscribe => File['/etc/redis.conf'], } } // 在主节点调用redis::master(注释第2行),在从节点调用redis::slave(注释第1行),当前为主节点上的配置 include redis::master #include redis::slave puppet apply -v --noop redis.pp puppet apply -v redis.pp ss -ntl | grep 6379 redis-cli
测试:在主节点添加键值对:mykey, "hello redis" , 在从节点成功查看mykey的值
主节点结果
从节点结果
七、Puppet的模板:
erb(embedded ruby):模板语言,属于嵌入式语言,类似ansible中jinja2语言的作用
puppet兼容的erb语法:
file{'title':
ensure => file,
path => '/PATH/TO/FILE',
content => template('/PATH/TO/ERB_FILE'),
}
文本文件中内嵌变量替换机制:
<%= @VARIABLE_NAME %>-
实验14:实现nginx配置文件中的worker_process的值随被配置主机的硬件情况调整
mkdir nginx.module cd nginx.module/ cp /etc/nginx/nginx.conf ./nginx.conf.erb vim nginx.conf.erb worker_processes <%= @processorcount %>; vim nginx.pp class nginx { package{'nginx': ensure => latest, } -> file{'/etc/nginx/nginx.conf': ensure => file, content => template('/root/nginx.module/nginx.conf.erb'), } ~> service {'nginx': ensure => running, } } include nginx puppet apply -v --noop nginx.pp puppet apply -v nginx.pp ss -ntlp | grep nginx grep 'worker_processes' /etc/nginx/nginx.conf
可以看到/etc/nginx/nginx.conf配置文件worker_processes的值自动按照被配置主机的CPU核心数配置
-
实验15:在实验13-3的基础上,修改子类redis::slave的定义,实现在定义时修改主节点ip和端口号,而不需要每次变动时修改配置文件
vim /root/redis.module/redis.pp class redis { package{'redis': ensure => latest, } -> service{'redis': ensure => running, enable => true, hasrestart => true, restart => 'service redis restart', } } class redis::master inherits redis { file{'/etc/redis.conf': ensure => file, source => '/root/redis.module/redis-master.conf', owner => redis, group => root, require => Package['redis'], } Service['redis'] { restart => 'systemctl restart redis.service', subscribe => File['/etc/redis.conf'], } } class redis::slave($masterip,$masterport) inherits redis { file{'/etc/redis.conf': ensure => file, content => template('/root/redis.module/redis-slave.conf'), // 使用模板 owner => redis, group => root, require => Package['redis'], } Service['redis'] { restart => 'systemctl restart redis.service', subscribe => File['/etc/redis.conf'], } } #include redis::master class {'redis::slave': // 向子类传递参数 masterip => '192.168.136.230', masterport => '6379', } vim /root/redis.module/redis-slave.conf slaveof <%= @masterip %> <%= @masterport %> puppet apply -v --noop redis.pp puppet apply -v redis.pp