一、puppet 介绍

1、puppet是什么

puppet是一个IT基础设施自动化管理工具,它能够帮助系统管理员管理基础设施的整个生命周期: 供应(provisioning)、配置(configuration)、联动(orchestration)及报告(reporting)。
  基于puppet ,可实现自动化重复任务、快速部署关键性应用以及在本地或云端完成主动管理变更和快速扩展架构规模等。
  遵循GPL 协议(2.7.0-), 基于ruby语言开发。
  2.7.0 以后使用(Apache 2.0 license)
  对于系统管理员是抽象的,只依赖于rubyfacter
  能管理多达40 多种资源,例如:fileusergrouphostpackageservicecronexecyum repo等。

2、puppet的工作机制

1)工作模型

puppet 通过声明性、基于模型的方法进行IT自动化管理。
  定义:通过puppet 的声明性配置语言定义基础设置配置的目标状态;
  模拟:强制应用改变的配置之前先进行模拟性应用;
  强制:自动、强制部署达成目标状态,纠正任何偏离的配置;
  报告:报告当下状态及目标状态的不同,以及达成目标状态所进行的任何强制性改变;
puppet三层模型
  puppet三层模型如下:

Puppet - 图1

puppet三层模型

2)工作流程

Puppet - 图2

工作流程

3)使用模型

puppet的使用模型分为单机使用模型master/agent模型,下面我们来看看这两个模型的原理图。
单机使用模型
  实现定义多个manifests —> complier —> catalog —> apply

Puppet - 图3

单机使用模型工作原理

master/agent模型
  master/agent模型实现的是集中式管理,即 agent 端周期性向 master 端发起请求,请求自己需要的数据。然后在自己的机器上运行,并将结果返回给 master 端。
  架构和工作原理如下:
架构

Puppet - 图4

master/agent模式架构

工作原理

Puppet - 图5

master/agent模式工作原理

3、puppet 名词解释

  • 资源:是puppet的核心,通过资源申报,定义在资源清单中。相当于ansible中的模块,只是抽象的更加彻底。
  • 类:一组资源清单。
  • 模块:包含多个类。相当于ansible中的角色
  • 站点清单:以主机为核心,应用哪些模块。

二、puppet 资源详解

接下来,我们就以单机模式来具体介绍一下puppet的各个部分。

1、程序安装及环境

首先,我们还是来安装一下puppetpuppet的安装可以使用源码安装,也可以使用rpm(官方提供)、epel源、官方提供的yum仓库来安装(通过下载官方提供的rpm包可以指定官方的yum仓库)。
  在这里,我们就是用 yum 安装的方式。

  1. yum install -y puppet

安装完成过后,我们可以通过rpm -ql puppet | less来查看一下包中都有一些什么文件。
  其中主配置文件为/etc/puppet/puppet.conf,使用的主程序为/usr/bin/puppet

2、puppet 资源简介

1)资源抽象

puppet 从以下三个维度来对资源完成抽象:

  1. 相似的资源被抽象成同一种资源“类型” ,如程序包资源、用户资源及服务资源等;
  2. 将资源属性或状态的描述与其实现方式剥离开来,如仅说明安装一个程序包而不用关心其具体是通过yum、pkgadd、ports或是其它方式实现;
  3. 仅描述资源的目标状态,也即期望其实现的结果,而不是其具体过程,如“确定nginx 运行起来” 而不是具体描述为“运行nginx命令将其启动起来”;

这三个也被称作puppet 的资源抽象层(RAL)
  RAL 由type( 类型) 和provider( 提供者,即不同OS 上的特定实现)组成。

2)资源定义

资源定义通过向资源类型的属性赋值来实现,可称为资源类型实例化;
  定义了资源实例的文件即清单,manifest;
  定义资源的语法如下:

  1. type {'title':
  2. attribute1 => value1,
  3. atrribute2 => value2,
  4. ……
  5. }

注意:type必须使用小写字符;title是一个字符串,在同一类型中必须惟一;每一个属性之间需要用“,”隔开,最后一个“,”可省略。
  例如,可以同时有名为nginx 的“service”资源和“package”资源,但在“package” 类型的资源中只能有一个名为“nginx”的资源。

3)资源属性中的三个特殊属性:

  • Namevar:可简称为name;
  • ensure:资源的目标状态;
  • Provider:指明资源的管理接口;

3、常用资源总结

1)查看资源
  我们可以使用puppet describe来打印有关Puppet资源类型,提供者和元参数的帮助。使用语法如下:

  1. puppet describe [-h|--help] [-s|--short] [-p|--providers] [-l|--list] [-m|--meta] [type]
  2. -l:列出所有资源类型;
  3. -s:显示指定类型的简要帮助信息;
  4. -m:显示指定类型的元参数,一般与-s一同使用;

2)group:管理系统上的用户组。
  查看使用帮助信息:

Puppet - 图6

group使用帮助

  1. 属性:
  2. name:组名,可以省略,如果省略,将继承title的值;
  3. gidGID
  4. system:是否为系统组,true OR false
  5. ensure:目标状态,present/absent
  6. members:成员用户;

简单举例如下:

  1. vim group.pp
  2. group{'mygrp':
  3. name => 'mygrp',
  4. ensure => present,
  5. gid => 2000,
  6. }

我们可以来运行一下:

Puppet - 图7

运行写好的group资源

3)user:管理系统上的用户。
  查看使用帮助信息:

Puppet - 图8

user使用帮助

  1. 属性:
  2. name:用户名,可以省略,如果省略,将继承title的值;
  3. uid: UID;
  4. gid:基本组ID
  5. groups:附加组,不能包含基本组;
  6. comment:注释;
  7. expiry:过期时间
  8. home:用户的家目录;
  9. shell:默认shell类型;
  10. system:是否为系统用户
  11. ensurepresent/absent
  12. password:加密后的密码串;

简单举例如下:

  1. vim user1.pp
  2. user{'keerr':
  3. ensure => present,
  4. system => false,
  5. comment => 'Test User',
  6. shell => '/bin/tcsh',
  7. home => '/data/keerr',
  8. managehome => true,
  9. groups => 'mygrp',
  10. uid => 3000,
  11. }

4)package:puppet的管理软件包。
  查看使用帮助信息:

Puppet - 图9

package使用帮助

  1. 属性:
  2. ensureinstalled, present, latest, absent, any version string (implies present)
  3. name:包名,可以省略,如果省略,将继承title的值;
  4. source:程序包来源,仅对不会自动下载相关程序包的provider有用,例如rpmdpkg
  5. provider:指明安装方式;

简单举例如下:

  1. vim package1.pp
  2. package{'nginx':
  3. ensure => installed,
  4. procider => yum
  5. }

5)service:定义服务的状态
  查看使用帮助信息:

  1. puppet describe service -s -m

Puppet - 图10

service使用帮助

  1. 属性:
  2. ensure:服务的目标状态,值有truerunning)和falsestopped
  3. enable:是否开机自动启动,值有truefalse
  4. name:服务名称,可以省略,如果省略,将继承title的值
  5. path:服务脚本路径,默认为/etc/init.d/下
  6. start:定制启动命令
  7. stop:定制关闭命令
  8. restart:定制重启命令
  9. status:定制状态

简单举例如下:

  1. vim service1.pp
  2. service{'nginx':
  3. ensure => true,
  4. enable => false
  5. }

6)file:管理文件、目录、软链接
  查看使用帮助信息:

Puppet - 图11

file使用帮助

  1. 属性:
  2. ensure:目标状态,值有absent,present,file,directorylink
  3. file:类型为普通文件,其内容由content属性生成或复制由source属性指向的文件路径来创建;
  4. link:类型为符号链接文件,必须由target属性指明其链接的目标文件;
  5. directory:类型为目录,可通过source指向的路径复制生成,recurse属性指明是否递归复制;
  6. path:文件路径;
  7. source:源文件;
  8. content:文件内容;
  9. target:符号链接的目标文件;
  10. owner:定义文件的属主;
  11. group:定义文件的属组;
  12. mode:定义文件的权限;
  13. atime/ctime/mtime:时间戳;

简单举例如下:

  1. vim file1.pp
  2. file{'aaa':
  3. path => '/data/aaa',
  4. source => '/etc/aaa',
  5. owner => 'keerr',
  6. mode => '611',
  7. }

7)exec:执行命令,慎用。通常用来执行外部命令
  查看使用帮助信息:

  1. puppet describe exec -s -m

Puppet - 图12

exec使用帮助

  1. 属性:
  2. command(namevar):要运行的命令;
  3. cwd:指定运行该命令的目录;
  4. creates:文件路径,仅此路径表示的文件不存在时,command方才执行;
  5. user/group:运行命令的用户身份;
  6. path:指定命令执行的搜索路径;
  7. onlyif:此属性指定一个命令,此命令正常(退出码为0)运行时,当前command才会运行;
  8. unless:此属性指定一个命令,此命令非正常(退出码为非0)运行时,当前command才会运行;
  9. refresh:重新执行当前command的替代命令;
  10. refreshonly:仅接收到订阅的资源的通知时方才运行;

简单举例如下:

  1. vim exec1.pp
  2. exec{'cmd':
  3. command => 'mkdir /data/testdir',
  4. path => ['/bin','/sbin','/usr/bin','/usr/sbin'],
  5. # path => '/bin:/sbin:/usr/bin:/usr/sbin',
  6. }

8)cron:定义周期性任务
  查看使用帮助信息:

Puppet - 图13

cron使用帮助

  1. 属性:
  2. command:要执行的任务(命令或脚本);
  3. ensure:目标状态,present/absent
  4. hour:时;
  5. minute:分;
  6. monthday:日;
  7. month:月;
  8. weekday:周;
  9. user:以哪个用户的身份运行命令(默认为root);
  10. target:添加为哪个用户的任务;
  11. namecron job的名称;

简单举例如下:

  1. vim cron1.pp
  2. cron{'timesync':
  3. command => '/usr/sbin/ntpdata 172.16.0.1',
  4. ensure => present,
  5. minute => '*/3',
  6. user => 'root',
  7. }

我们可以运行一下,查看我们的crontab,来看看该任务是否已经被添加:

  1. [root@master manifests]# puppet apply -v --noop cron1.pp #试运行
  2. [root@master manifests]# puppet apply -v cron1.pp #运行
  3. [root@master manifests]# crontab -l #查看计划任务
  4. # HEADER: This file was autogenerated at 2017-12-14 15:05:05 +0800 by puppet.
  5. # HEADER: While it can still be managed manually, it is definitely not recommended.
  6. # HEADER: Note particularly that the comments starting with 'Puppet Name' should
  7. # HEADER: not be deleted, as doing so could cause duplicate cron jobs.
  8. # Puppet Name: timesync
  9. */3 * * * * /usr/sbin/ntpdata 172.16.0.1

9)notify:调试输出
  查看使用帮助信息:

Puppet - 图14

  1. 属性:
  2. message:记录的信息
  3. name:信息名称

该选项一般用于master/agent模式中,来记录一些操作的时间,比如重新安装了一个程序呀,或者重启了应用等等。会直接输出到代理机的运行日志中。


以上,就是我们常见的8个资源。其余的资源我们可以使用puppet describe -l来列出,上文中也已经说过了~

4、资源的特殊属性

puppet中也提供了beforerequirenotifysubscribe四个参数来定义资源之间的依赖关系和通知关系。

before:表示需要依赖于某个资源 require:表示应该先执行本资源,在执行别的资源 notify:A notify B:B依赖于A,且A发生改变后会通知B; subscribe:B subscribe A:B依赖于A,且B监控A资源的变化产生的事件;

同时,依赖关系还可以使用->~>来表示:

-> 表示后资源需要依赖前资源 ~> 表示前资源变动通知后资源调用

举例如下:

  1. vim file.pp
  2. file{'test.txt': #定义一个文件
  3. path => '/data/test.txt',
  4. ensure => file,
  5. source => '/etc/fstab',
  6. }
  7. file{'test.symlink': #依赖文件建立超链接
  8. path => '/data/test.symlink',
  9. ensure => link,
  10. target => '/data/test.txt',
  11. require => File['test.txt'],
  12. }
  13. file{'test.dir': #定义一个目录
  14. path => '/data/test.dir',
  15. ensure => directory,
  16. source => '/etc/yum.repo.d/',
  17. recurse => true,
  18. }

我们还可以使用在最下面统一写依赖关系的方式来定义:

  1. vim redis.pp
  2. package{'reids':
  3. ensure => installed,
  4. }
  5. file{'/etc/redis.conf':
  6. source => '/root/manifets/files/redis.conf',
  7. ensure => file,
  8. owner => redis,
  9. group => root,
  10. mode => '0640',
  11. }
  12. service{'redis':
  13. ensure => running,
  14. enable => true,
  15. hasrestart => true,
  16. }
  17. Package['redis'] -> File['/etc/redis.conf'] -> Service['redis'] #定义依赖关系

tag 标签

如同 anssible 一样,puppet 也可以定义“标签”——tag,打了标签以后,我们在运行资源的时候就可以只运行某个打过标签的部分,而非全部。这样就更方便于我们的操作。
 一个资源中,可以有一个tag也可以有多个。具体使用语法如下:

  1. type{'title':
  2. ...
  3. tag => 'TAG1',
  4. }
  5. type{'title':
  6. ...
  7. tag => ['TAG1','TAG2',...],
  8. }

调用时的语法如下:

  1. puppet apply --tags TAG1,TAG2,... FILE.PP

实例
  首先,我们去修改一下redis.pp文件,添加一个标签进去

  1. vim redis.pp
  2. package{'redis':
  3. ensure => installed,
  4. }
  5. file{'/etc/redis.conf':
  6. source => '/root/manifets/file/redis.conf',
  7. ensure => file,
  8. owner => redis,
  9. group => root,
  10. mode => '0640',
  11. tag => 'instconf' #定义标签
  12. }
  13. service{'redis':
  14. ensure => running,
  15. enable => true,
  16. hasrestart => true,
  17. }
  18. Package['redis'] -> File['/etc/redis.conf'] -> Service['redis']

然后,我们手动先开启redis服务:

  1. systemctl start redis

现在,我们去修改一下file目录下的配置文件:

  1. vim file/redis.conf
  2. requirepass keerya

接着,我们就去运行redis.pp,我们的配置文件已经修改过了,现在想要实现的就是重启该服务,实现,需要使用密码keer登录:

  1. puppet apply -v --tags instconf redis.pp

Puppet - 图15

redis.pp运行结果

现在,我们就去登录一下redis看看是否生效:

  1. redis-cli -a keerya

Puppet - 图16

redis验证

验证成功,实验完成。

5、puppet 变量

puppet 变量以“$”开头,赋值操作符为“=”,语法为$variable_name=value
数据类型:

字符型:引号可有可无;但单引号为强引用双引号为弱引用;支持转义符;   数值型:默认均识别为字符串,仅在数值上下文才以数值对待;   数组:[]中以逗号分隔元素列表;   布尔型值:true, false;不能加引号;   hash:{}中以逗号分隔k/v数据列表; 键为字符型,值为任意puppet支持的类型;{ ‘mon’ => ‘Monday’, ‘tue’ => ‘Tuesday’, };   undef:从未被声明的变量的值类型;

正则表达式:

(?:)   (?-:)   OPTIONS:     i:忽略字符大小写;     m:把.当换行符;     x:忽略中的空白字符;   (?i-mx:PATTERN) 注意:不能赋值给变量,仅能用在接受=~!~操作符的位置;

1)puppet的变量种类

puppet 种类有三种,为facts内建变量用户自定义变量
facts:
  由facter提供;top scope;
内建变量:
  master端变量
    $servername, $serverip, $serverversion
  agent端变量
    $clientcert, $clientversion, $environment
  parser变量
    $module_name
用户自定义变量

2)变量的作用域

不同的变量也有其不同的作用域。我们称之为Scope
  作用域有三种,top scope,node scope,class scope。
  其生效范围排序为:top scope > node scope > class scope

Puppet - 图17

变量生效范围

其优先级排序为:top scope < node scope < class scope

6、puppet 流程控制语句

puppet 支持if 语句case 语句selector 语句

1)if 语句

if语句支持单分支,双分支和多分支。具体语法如下:

  1. 单分支:
  2. if CONDITION {
  3. statement
  4. ……
  5. }
  6. 双分支:
  7. if CONDITION {
  8. statement
  9. ……
  10. }
  11. else{
  12. statement
  13. ……
  14. }
  15. 多分支:
  16. if CONDITION {
  17. statement
  18. ……
  19. }
  20. elsif CONDITION{
  21. statement
  22. ……
  23. }
  24. else{
  25. statement
  26. ……
  27. }

其中,CONDITION的给定方式有如下三种:

  • 变量
  • 比较表达式
  • 有返回值的函数

举例

  1. vim if.pp
  2. if $operatingsystemmajrelease == '7' {
  3. $db_pkg='mariadb-server'
  4. }else{
  5. $db_pkg='mysql-server'
  6. }
  7. package{"$db_pkg":
  8. ensure => installed,
  9. }

2)case 语句

类似 if 语句,case 语句会从多个代码块中选择一个分支执行,这跟其它编程语言中的 case 语句功能一致。
  case 语句会接受一个控制表达式和一组 case 代码块,并执行第一个匹配到控制表达式的块。
  使用语法如下:

  1. case CONTROL_EXPRESSION {
  2. case1: { ... }
  3. case2: { ... }
  4. case3: { ... }
  5. ……
  6. default: { ... }
  7. }

其中,CONTROL_EXPRESSION的给定方式有如下三种:

  • 变量
  • 表达式
  • 有返回值的函数

各case的给定方式有如下五种:

  • 直接字串;
  • 变量
  • 有返回值的函数
  • 正则表达式模式;
  • default

举例

  1. vim case.pp
  2. case $osfamily {
  3. "RedHat": { $webserver='httpd' }
  4. /(?i-mx:debian)/: { $webserver='apache2' }
  5. default: { $webserver='httpd' }
  6. }
  7. package{"$webserver":
  8. ensure => installed, before => [ File['httpd.conf'], Service['httpd'] ],
  9. }
  10. file{'httpd.conf':
  11. path => '/etc/httpd/conf/httpd.conf',
  12. source => '/root/manifests/httpd.conf',
  13. ensure => file,
  14. }
  15. service{'httpd':
  16. ensure => running,
  17. enable => true, restart => 'systemctl restart httpd.service',
  18. subscribe => File['httpd.conf'],
  19. }

3)selector 语句

Selector 只能用于期望出现直接值(plain value) 的地方,这包括变量赋值、资源属性、函数参数、资源标题、其它 selector。
  selector 不能用于一个已经嵌套于于selector 的case 中,也不能用于一个已经嵌套于case 的case 语句中。
  具体语法如下:

  1. CONTROL_VARIABLE ? {
  2. case1 => value1,
  3. case2 => value2,
  4. ...
  5. default => valueN,
  6. }

其中,CONTROL_EXPRESSION的给定方式有如下三种:

  • 变量
  • 表达式
  • 有返回值的函数

各case的给定方式有如下五种:

  • 直接子串;
  • 变量;
  • 有返回值的函数;
  • 正则表达式模式;
  • default

selectors 使用要点:

  1. 整个selector 语句会被当作一个单独的值,puppet 会将控制变量按列出的次序与每个case 进行比较,并在遇到一个匹配的 case 后,将其值作为整个语句的值进行返回,并忽略后面的其它 case。
  2. 控制变量与各 case 比较的方式与 case 语句相同,但如果没有任何一个 case 与控制变量匹配时,puppet 在编译时将会返回一个错误,因此,实践中,其必须提供default case。
  3. selector 的控制变量只能是变量或有返回值的函数,切记不能使用表达式。
  4. 其各 case 可以是直接值(需要加引号) 、变量、能调用返回值的函数、正则表达式模式或 default。
  5. 但与 case 语句所不同的是,selector 的各 case 不能使用列表。
  6. selector 的各 case 的值可以是一个除了 hash 以外的直接值、变量、能调用返回值的函数或其它的 selector。

举例

  1. vim selector.pp
  2. $pkgname = $operatingsystem ? {
  3. /(?i-mx:(ubuntu|debian))/ => 'apache2',
  4. /(?i-mx:(redhat|fedora|centos))/ => 'httpd',
  5. default => 'httpd',
  6. }
  7. package{"$pkgname":
  8. ensure => installed,
  9. }

三、class 类

1)什么是类?

类是puppet中命名的代码模块,常用于定义一组通用目标的资源,可在puppet全局调用;
  类可以被继承,也可以包含子类;
  具体定义的语法如下:

  1. class NAME{
  2. ... puppet code ...
  3. }

其中,在我们定义的时候,需要注意的是:

  • 类的名称只能以小写字母开头,可以包含小字字母、数字和下划线。
  • 每个类都会引入一个新的变量scope ,这意味着在任何时候访问类中的变量时,都得使用其完全限定名称。
    • 不过,在本地 scope 可以重新为 top scope 中的变量赋予一个新值。

下面,我们来看一个简单的例子:

  1. vim class1.pp
  2. class redis { #定义一个类
  3. package{'redis':
  4. ensure => installed,
  5. } ->
  6. file{'/etc/redis.conf':
  7. ensure => file,
  8. source => '/root/manifests/file/redis.conf',
  9. owner => 'redis',
  10. group => 'root',
  11. mode => '0640',
  12. tag => 'redisconf'
  13. } ~>
  14. service{'redis':
  15. ensure => running,
  16. enable => true,
  17. hasrestart => true,
  18. hasstatus => true
  19. }
  20. }
  21. include redis #调用类

注意:类只有被调用才会执行。include后可以跟多个类,直接用”,”隔开即可。

2)带有参数的类

我们定义的类也可以进行参数设置,可以进行参数的传递。
  具体语法如下所示:

  1. class NAME(parameter1, parameter2) { #注意,大括号前有一个空格
  2. ...puppet code...
  3. }

我们来看一个例子:

  1. vim class2.pp
  2. class instpkg($pkg) {
  3. package{"$pkg":
  4. ensure => installed,
  5. }
  6. }
  7. class{"instpkg": #给参数传入值
  8. pkg => 'memcached',
  9. }

注意:单个主机上不能被直接声明两次
  如果对应的参数未传值的话,执行会报错。
  但是我们可以在定义形参的时候,设定一个默认值,这样的话,我们不传入值的话,就会自动调用默认值:

  1. vim class3.pp
  2. class instpkg($pkg='wget') {
  3. package{"$pkg":
  4. ensure => installed,
  5. }
  6. }
  7. include instpkg

这样的话,我们直接使用include调用即可,就不需要给参数传入值了。
  由上,我们可以总结出,调用类的方式有两种

  1. 1. include CLASS_NAME1, CLASS_NAME2, ...
  2. 2. class{'CLASS_NAME':
  3. attribute => value,
  4. }

我们来看一个比较全面的例子:
  首先,判断我们系统的版本,是6还是7,由此来确定,是安装mysql还是mariadb,同时,使用调用参数的方式来实现如上需求。
  具体实现的代码如下:

  1. vim dbserver.pp
  2. class dbserver($dbpkg='mariadb-server',$svc='mariadb') { #定义类并给参数赋值
  3. package{"$dbpkg":
  4. ensure => installed,
  5. }
  6. service{"$svc":
  7. ensure => running,
  8. enable => true,
  9. hasrestart => true,
  10. hasstatus => true,
  11. }
  12. }
  13. if $operatingsystem == 'CentOS' {
  14. if $operatingsystemmajrelease == '7' {
  15. include dbserver #直接调用类
  16. } else {
  17. class{"dbserver": #调用类并对参数重新赋值
  18. dbpkg => 'mysql-server',
  19. svc => 'mysqld'
  20. }
  21. }
  22. }

3)类的继承

类似于其它编程语言中的类的功能,puppet 的Class 可以被继承,也可以包含子类。
  其定义的语法如下:

  1. class SUB_CLASS_NAME inherits PARENT_CLASS_NAME {
  2. ...puppet code...
  3. }

下面我们来看一个例子:

  1. vim class4.pp
  2. class redis { #定义class类
  3. package{'redis':
  4. ensure => installed,
  5. }
  6. service{'redis':
  7. ensure => running,
  8. enable => true,
  9. }
  10. }
  11. class redis::master inherits redis { #调用父类
  12. file {'/etc/redis.conf':
  13. ensure => file,
  14. source => '/root/manifests/file/redis-master.conf',
  15. owner => 'redis',
  16. group => 'root',
  17. }
  18. Service['redis'] { #定义依赖关系
  19. subscribe => File['/etc/redis.conf']
  20. }
  21. }
  22. class redis::slave inherits redis { #调用父类
  23. file {'/etc/redis.conf':
  24. ensure => file,
  25. source => '/root/manifests/file/redis-slave.conf',
  26. owner => 'redis',
  27. group => 'root',
  28. }
  29. Service['redis'] { #定义依赖关系
  30. subscribe => File['/etc/redis.conf']
  31. }
  32. }

一样的,我们的类在调用的时候,可以实现修改原有值额外新增属性的功能。

1.新增属性

我们的继承父类的时候,可以定义一些父类原本没有的属性:

Puppet - 图18

新增属性

2.新增原有值

在继承的类中,我们可以在属性原有值的基础上,使用 +> 进行新增修改:

Puppet - 图19

新增原有值

3.修改原有值

在继承的类中,我们可以直接把原有的值进行覆盖修改,使用 =>进行覆盖即可:

Puppet - 图20

修改原有值

4.整体调用父类,并重写部分值

在继承的类中,我们还可以在整体调用的基础上,根据不同的需求,把父类中的部分值进行重写修改:

Puppet - 图21

整体调用父类,并重写部分值

四、模板

模板通常以erb结尾。模板均使用erb语法。
  关于puppet兼容的erb语法,我们可以去官方文档查看,下面附上官方文档地址:https://docs.puppet.com/puppet/latest/reference/lang_template_erb.html
  以下,附上部分重要内容:

  1. <%= EXPRESSION %> — 插入表达式的值,进行变量替换
  2. <% CODE %> — 执行代码,但不插入值
  3. <%# COMMENT %> — 插入注释
  4. <%% or %%> — 插入%

接着我们来看一个实例:

实例1:puppet 模板实现修改 redis 端口地址

我们使用puppet 模板来实现,将redis 监听端口修改为本机的ip地址。
  首先,我们先来定义一个file.pp文件,在该文件中调用我们的模板:

  1. vim file.pp
  2. file{'/tmp/redis.conf': #仅用于测试模板是否生效,所以放在tmp目录下
  3. ensure => file,
  4. content => template('/root/manifests/file/redis.conf.erb'), #调用模板文件
  5. owner => 'redis',
  6. group => 'root',
  7. mode => '0640',
  8. }

接着,我们去修改配置文件的源,也就是我们的模板文件:

  1. vim file/redis.conf.erb
  2. bind 127.0.0.1 <%= @ipaddress_eth0 %> #修改监听端口

修改完成以后,我们就可以执行查看结果了:

然后,我们去查看一下/tmp/redis.conf文件:

Puppet - 图22

监听端口

可以看出,我们的变量替换已经成功。

五、模块

1)什么是模块?

实践中,一般需要把manifest 文件分解成易于理解的结构,例如将类文件、配置文件甚至包括后面将提到的模块文件等分类存放,并且通过某种机制在必要时将它们整合起来。
  这种机制即模块,它有助于以结构化、层次化的方式使用puppet,而puppet 则基于“模块自动装载器”。
  从另一个角度来说,模块实际上就是一个按约定的、预定义的结构存放了多个文件或子目录的目录,目录里的这些文件或子目录必须遵循其命名规范

2)模块的命名规范

模块的目录格式如下:

Puppet - 图23

目录格式

其中,每个文件夹中存放的内容及其要求如下:

  • MODULE NAME:模块名称,模块名只能以小写字母开头,可以包含小写字母、数字和下划线;但不能使用”main”和”settings”;
  • manifests/:必须要有
    • init.pp:必须一个类定义,类名称必须与模块名称相同;
  • files/:静态文件;
    • 其中,每个文件的访问路径遵循:puppet:///modules/MODULE_NAME/FILE_NAME
  • templates/
    • 其中,每个文件的访问路径遵循:tempate('MOD_NAME/TEMPLATE_FILE_NAME')
  • lib/:插件目录,常用于存储自定义的facts以及自定义类型;
  • spec/:类似于tests目录,存储lib/目录下插件的使用帮助和范例;
  • tests/:当前模块的使用帮助或使用范例文件;

实例:定义一个redis主从模块

下面我们就来看一个实例来具体的了解应该如何定义一个模块:
1)我们先来创建对应的目录格式:

  1. [root@master ~]# mkdir modules
  2. [root@master ~]# cd modoules/
  3. [root@master modules]# ls
  4. [root@master modules]# mkdir -pv redis/{manifests,files,templates,tests,lib,spec}
  5. mkdir: created directory redis
  6. mkdir: created directory redis/manifests
  7. mkdir: created directory redis/files
  8. mkdir: created directory redis/templates
  9. mkdir: created directory redis/tests
  10. mkdir: created directory redis/lib
  11. mkdir: created directory redis/spec

2)目录格式创建完成之后,我们就可以来创建对应的父类子类文件了。
  首先,我们来创建父类文件:

  1. [root@master modules]# cd redis/
  2. [root@master redis]# vim manifests/init.pp
  3. class redis {
  4. package{'redis':
  5. ensure => installed,
  6. } ->
  7. service{'redis':
  8. ensure => running,
  9. enable => true,
  10. hasrestart => true,
  11. hasstatus => true,
  12. require => Package['redis'],
  13. }
  14. }

创建完成后,我们再来创建对应的子类文件:

  1. [root@master redis]# vim manifests/master.pp
  2. class redis::master inherits redis {
  3. file {'/etc/redis.conf':
  4. ensure => file,
  5. source => 'puppet:///modules/redis/redis-master.conf',
  6. owner => 'redis',
  7. group => 'root',
  8. mode => '0640',
  9. }
  10. Package['redis'] -> File['/etc/redis.conf'] ~> Service['redis']
  11. }
  12. [root@master redis]# vim manifests/slave.pp
  13. class redis::slave($master_ip,$master_port='6379') inherits redis {
  14. file {'/etc/redis.conf':
  15. ensure => file,
  16. content => template('redis/redis-slave.conf.erb'),
  17. owner => 'redis',
  18. group => 'root',
  19. mode => '0640',
  20. }
  21. Package['redis'] -> File['/etc/redis.conf'] ~> Service['redis']
  22. }

3)准备文件:
  现在我们需要把模板文件准备好,放入我们的templates目录下:

  1. scp redis.conf.erb /root/modules/redis/templates/redis-slave.conf.erb

还有我们的静态文件,也要放入我们的files目录下:

  1. scp redis.conf /root/modules/redis/files/redis-master.conf

4)查看目录结构,确定我们是否都已准备完成:

  1. [root@master modules]# tree
  2. .
  3. └── redis
  4. ├── files
  5. └── redis-master.conf
  6. ├── lib
  7. ├── manifests
  8. ├── init.pp
  9. ├── master.pp
  10. └── slave.pp
  11. ├── spec
  12. ├── templates
  13. └── redis-slave.conf.erb
  14. └── tests
  15. 7 directories, 5 files

5)现在就可以把我们的准备好的模块放入系统的模块目录下:

  1. [root@master mdoules]# cp -rp redis/ /etc/puppet/modules/

注意,模块是不能直接被调用的,只有放在/etc/puppet/modules下,或/usr/share/puppet/modules目录下,使其生效才可以被调用。
  我们可以来查看一下我们的模块到底有哪些:

  1. [root@master mdoules]# puppet module list
  2. /etc/puppet/modules
  3. └── redis (???)
  4. /usr/share/puppet/modules (no modules installed)

可以看出,我们的模块已经定义好了,现在我们就可以直接调用了。
6)调用模块
  我们可以直接命令行传入参数来调用我们准备好的模块:

  1. [root@master modules]# puppet apply -v --noop -e "class{'redis::slave': master_ip => '192.168.37.100'}" #如果有多个参数,直接以逗号隔开即可

也可以把我们的调用的类赋值在.pp文件中,然后运行该文件。

  1. [root@master ~]# cd manifests/
  2. [root@master manifests]# vim redis2.pp
  3. class{'redis::slave':
  4. master_ip => '192.168.37.100',
  5. }
  6. [root@master manifests]# puppet apply -e --noop redis2.pp

以上。实验完成。
  注意,以上实验是我们在单机模式下进行的,如果是要在master/agent 模式下进行,步骤还会略有不同。

六、master/agent 模型

master/agent模型时通过主机名进行通信的,下面,就来看看 master-agent 模式的puppet运维自动化如何实现:

实现步骤

1、实现前准备

1)下载包
master 端:puppet.noarchpuppet-server.noarch
agent 端:puppet.noarch

Puppet - 图24

puppet包查询

2)主机名解析
  为了方便我们后期的操作,我们可以通过定义/etc/hosts文件实现主机名的解析。如果机器很多的话,可以使用DNS进行解析。

  1. [root@master ~]# vim /etc/hosts
  2. 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
  3. ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
  4. 192.168.37.111 master.keer.com
  5. 192.168.37.122 server1.keer.com

注意,该操作需要在每一台主机上进行。
  修改完成以后,我们可以来测试一下是否已经成功:

  1. [root@master ~]# ping server1.keer.com

Puppet - 图25

连通性测试

3)时间同步

  1. [root@master ~]# systemctl start chronyd.service

所有机器上都开启chronyd.service服务来进行时间同步
  开启过后可以查看一下状态:

  1. [root@master ~]# systemctl status chronyd.service

Puppet - 图26

时间同步状态

我们可以使用chronyc sources命令来查看时间源:

Puppet - 图27

查看时间源

2、开启 master 端的 puppet 服务

1)手动前台开启,观察服务开启过程:

  1. puppet master -v --no-daemonize #前台运行

Puppet - 图28

整个过程都是自动完成的,其中,每一步的意思如下:
  ① 创建key 给CA
  ② 创建一个请求给CA
  ③ 自签名证书
  ④ CA 创建完成
  ⑤ 创建证书吊销列表
  ⑥ 为当前的master 主机签署证书
  ⑦ master 的证书签署完成

2)直接systemctl开启服务,监听在8140端口。

Puppet - 图29

开启服务

3、在 agent 端开启服务

1)在配置文件中指明server端的主机名:

  1. [root@server1 ~]# vim /etc/puppet/puppet.conf
  2. server = master.keer.com

Puppet - 图30

agent端配置文件

接着,我们可以通过puppet config print命令来打印输出我们配置的参数:

  1. [root@server1 ~]# puppet config print 显示配置文件中的配置参数
  2. [root@server1 ~]# puppet config print --section=main 显示main 段的配置参数
  3. [root@server1 ~]# puppet config print --section=agent 显示agent 段的配置参数
  4. [root@server1 ~]# puppet config print server 显示server 的配置参数

Puppet - 图31

打印输出参数

2)开启 agent 服务

Puppet - 图32

开启agent服务

我们可以发现,他会一直卡在这里等待CA颁发证书。

3)在 master 端签署证书

  1. [root@master ~]# puppet cert list
  2. "server1.keer.com" (SHA256) B5:67:51:30:5C:FB:45:BA:7A:73:D5:C5:87:D4:E3:1C:D7:02:BE:DD:CC:7A:E2:F0:28:34:87:86:EF:E7:1D:E4
  3. [root@master ~]# puppet cert sign server1.keer.com #颁发证书
  4. Notice: Signed certificate request for server1.keer.com
  5. Notice: Removing file Puppet::SSL::CertificateRequest server1.keer.com at '/var/lib/puppet/ssl/ca/requests/server1.keer.com.pem'

master 端管理证书部署的命令语法如下: puppet cert [–all|-a] []   action:     list 列出证书请求     sign 签署证书     revoke 吊销证书     clean 吊销指定的客户端的证书,并删除与其相关的所有文件;

注意:某agent证书手工吊销后重新生成一次;
  On master host:
    puppet cert revoke NODE_NAME
    puppet cert clean NODE_NAME
  On agent host:
    重新生成的主机系统,直接启动agent;
    变换私钥,建议先清理/var/lib/puppet/ssl/目录下的文件

4)终止服务开启,再次开启

  1. [root@server1 ~]# puppet agent -v --noop --no-daemonize

Puppet - 图33

开启agent端服务

可以看出我们的服务开启成功,但是由于master 端没有配置站点清单,所以没有什么动作。

4、配置站点清单,且测试agent 端是否实现

1)设置站点清单
① 查询站点清单应存放的目录,(可以修改,去配置文件修改)

  1. [root@master ~]# puppet config print |grep manifest

Puppet - 图34

查询配置文件的参数

  1. [root@master ~]# cd /etc/puppet/manifests/
  2. [root@master manifests]# vim site.pp
  3. node 'server1.along.com' {
  4. include redis::master
  5. }

分析:就是简单的调用模块,只有模块提前定义好就可以直接调用;我调用的是上边的redis 模块

2)给puppet 用户授权
  因为agent 端要来master 端读取配置,身份是puppet

  1. [root@master manifests]# chown -R puppet /etc/puppet/modules/redis/*

3)[root@server1 ~]# puppet agent -v —noop —no-daemonize 手动前台开启agent 端服务

Puppet - 图35

enter description here

(4)直接开启服务,agent 会自动去master 端获取配置
[root@server1 ~]# systemctl start puppetagent 包已下载,服务也开启了

Puppet - 图36

enter description here

实战

使用master-agent 模型完成完整的 redis 主从架构

1)环境准备

机器名称 IP配置 服务角色
puppet-master 192.168.37.111 puppet的master
puppet-server1-master-redis 192.168.37.122 puppet的agent,redis 的master
puppet-server2-slave-redis 192.168.37.133 puppet的agent,redis 的slave

2)实验前准备

1)下载包
master 端:puppet.noarchpuppet-server.noarch
agent 端:puppet.noarch

Puppet - 图37

puppet包查询

2)主机名解析
  为了方便我们后期的操作,我们可以通过定义/etc/hosts文件实现主机名的解析。如果机器很多的话,可以使用DNS进行解析。

  1. [root@master ~]# vim /etc/hosts
  2. 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
  3. ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
  4. 192.168.37.111 master.keer.com
  5. 192.168.37.122 server1.keer.com
  6. 192.168.37.133 server2.keer.com

注意,该操作需要在每一台主机上进行。
  修改完成以后,我们可以来测试一下是否已经成功:

  1. [root@master ~]# ping server1.keer.com

Puppet - 图38

连通性测试

3)时间同步

  1. [root@master ~]# systemctl start chronyd.service

三台机器上都开启chronyd.service服务来进行时间同步
  开启过后可以查看一下状态:

  1. [root@master ~]# systemctl status chronyd.service

Puppet - 图39

时间同步状态

我们可以使用chronyc sources命令来查看时间源:

Puppet - 图40

查看时间源

3)开启puppet 的master、agent 服务

(1)开启服务

  1. [root@master ~]# systemctl start puppetmaster
  2. [root@server1 ~]# systemctl start puppetagent
  3. [root@server2 ~]# systemctl start puppetagent

因为server2 是第一次连接,需master 端签署证书

(2)master 签署颁发证书

  1. [root@master manifests]# puppet cert list
  2. [root@master ~]# puppet cert sign server2.keer.com

Puppet - 图41

master 颁发证书

4)配置站点清单

  1. [root@master manifests]# cd /etc/puppet/manifests
  2. [root@master manifests]# vim site.pp 直接调上边完成的模块
  3. node 'server1.keer.com' {
  4. include redis::master
  5. }
  6. node 'server2.keer.com' {
  7. class{'redis::slave':
  8. master_ip => 'server1.keer.com'
  9. }
  10. }

5)检测主从架构

  1. [root@server2 ~]# vim /etc/redis.conf

Puppet - 图42

检测主从架构

  1. [root@server2 ~]# redis-cli -a keerya info Replication

Puppet - 图43

enter description here

6)再添加个模块准备配置进站点清单

(1) 创建一个 chrony 模块,前准备

  1. [root@master ~]# cd modules/ 进入模块工作目录
  2. [root@master modules]# mkdir chrony 创建chrony 的模块
  3. [root@master modules]# mkdir chrony/{manifests,files} -pv 创建模块结构

(2)配置chrony 模块

  1. [root@master modules]# cd chrony/
  2. [root@master chrony]# cp /etc/chrony.conf files/
  3. [root@master puppet]# vim files/chrony.conf
  4. # test #用于测试实验结果

Puppet - 图44

添加一行

  1. [root@master chrony]# vim manifests/init.pp
  2. class chrony {
  3. package{'chrony':
  4. ensure => installed
  5. } ->
  6. file{'/etc/chrony.conf':
  7. ensure => file,
  8. source => 'puppet:///modules/chrony.conf',
  9. owner => 'root',
  10. group => 'root',
  11. mode => '0644'
  12. } ~>
  13. service{'chronyd':
  14. ensure => running,
  15. enable => true,
  16. hasrestart => true,
  17. hasstatus => true
  18. }
  19. }

(3)puppet 添加这个模块,并生效

  1. [root@master modules]# cp -rp chrony/ /etc/puppet/modules/
  2. [root@master modules]# puppet module list

Puppet - 图45

查看puppet模块列表

7)再配置站点清单

  1. [root@master ~]# cd /etc/puppet/manifests/
  2. [root@master manifests]# vim site.pp
  3. node 'base' {
  4. include chrony
  5. }
  6. node 'server1.keer.com' inherits 'base' {
  7. include redis::master
  8. }
  9. node 'server2.keer.com' inherits 'base' {
  10. class{'redis::slave':
  11. master_ip => 'server1.keer.com'
  12. }
  13. }
  14. #node /cache[1-7]+\.keer\.com/ { #可以用正则匹配多个服务器使用模块
  15. # include varnish
  16. #}

8)测试

我们现在直接去server2机器上,查看我们的配置文件是否已经生效,是否是我们添加过一行的内容:

  1. [root@server2 ~]# vim /etc/chrony.conf

Puppet - 图46

server2上的内容

发现我们的实验成功。
https://www.cnblogs.com/keerya/