-------------------------------------------------------------------------------------------------------------------------------------

puppet

puppet

  一个经典的系统架构,像一个简单的站点,哪怕一个中小规模的站点,如果我们只有一个独立系统而没有做系统分割的话,那这个站点上大体上就有这几个层次组成。首先当用户请求到达时,前端做一个负载均衡器,有可能是一个HAproxy也有可能是反代的nginx,我们应该给他做高可用,他是总入口,如果成为单点的话,后果是很严重的,对这个城市来讲,我们也可以使用Keepalived做双主模型,高可用在前段在使用DNS做同一个域名下2条A记录的方式将用户请求给他分散到这个站点上来。对于客户端来讲,每一个浏览器对单个域名的访问通常它是有线程上限的。比如说我们通常讲浏览器是双线程的,或者浏览器是四线程,它通常指的是在同一个单个域名下,访问是双线程或者四线程,一般来讲双线程比较常见。所以说很多时候我们网站还会通常提供多个域名,比如说图片在images路径下,而主站在www路径下,这个应该想必应该都知道,对不对。那之所以如此呢,就是因为images是开头的是一个域名,而www开头的是另外一个域名,我们站的浏览器的角度来讲,因为他们实际上相当于两个域名,因此是可以四线程。假如说单域名是双线程的话,当然对于images而言最好的改成另外一个真正的域名,而不是说我们仅仅是把主机名(主机名至关重要,puppet是严重依赖于主机名的)给改一改。因为那个时候他还是同一个域内。      而对于负载均衡器而言,向后端开始做动静分离对所有的动态内容或者对于主站,我们应该给交由一组,一组动态物体来相应可以通常是tomcat或者是php的fpm,如果是php的fpm或者是tomcat,这其中的任何一个都有可能给他做成什么呢?比如说像tomcat每一个主机上应该是一个AT的组合,那如果是php话,那都应该是一个AP的组合。用户的请求通常不会直接送达,哪怕后端的php是fpm的通常也不会直接有前端的负载均衡器跟你的php打交道,而是在php每一台主机就前端都有可能还有一个Apache在做缓存代理,那tomcat亦是如尺,我们也不可能让tomcat直接工作在80或者8080端口上既负责处理动态内容,又得去响应处理http请求我们一般是不会这么做的。      再向后走了,用户的请求应该到达一个负载均衡器上。因为后端mysql主机可能不止一台,mysql有可能做主从如果做主从的话要做动静分离,如果我们做多种模型的话,其实我们前端作为一个负载均衡器就可以了。对于mysql来讲,不管是什么模型的,无论是主从也罢,还是我们做了多种模型的也罢。那从多个节点当中做负载均衡,为了能够提高其性能,通常需要对他的查询结果做缓存,所以在这里我们通常还要部署一个内存缓存(MemoryCache)主机用完实现做内存缓存,这样的可以极大的提升性能的。当然有些地方可能用的是redis之类的,但是缓存数据通常可能会用到MemoryCache这样组件用的还是非常多的,但是又说过MemoryCache他的智能性一半在客户端,一般在服务端,服务端仅仅提供的是缓存功能等等。但是不管怎么讲,这里有许许多多主机存在。而网站当中一旦用到些静态内容的话,我们会将静态内容分散给另外一台主机。而对于主机而言,静态内容可以高效缓存的,对不对。所以这里还会做一些缓存主机。那前端做静态内容向后端vanish分发时,我们要设定合用的算法,而后vanish主机也好,或者slave缓存服务器也罢,能够跟用户请求之间发送给后端的,一般是一个共享存储上的内容,而这个共享存储前端应该有一个反向代理像nginx或者是其他的web服务器都可以,那nginx比较常见,而此nginx主机还要做高可用,因为它是整个缓存代理入口的单点。这是静态内容,假如我们不考虑图片,或者文档内容做分离的话,他们就放在同一个位置好了。但是如果用户需要上传图片的话,那我们就需要用到另外一个上传专用的应用程序了,这个应用程序其实也有可能就在tomcat上,而上传的数据将直接送达至,通过api送给后端的分布式存储。好的,这就是我们一个简单的系统。那对于系统来讲,如果构架的节点是规模不太大的话,那么几十台主机应该也是很常见的,所以我将来如果任何一台主机出现故障,需要重新进行部署时应该怎么做呢,还有这台主机出现故障时,我想立即能够知道这样的消息,哪儿发生的故障又该怎么做呢?更何况我们对于整个系统来讲所需要了解统计数据或指标可能不仅仅是不是硬件故障。因为我们需要了解的,包括像硬件故障,软件是否ok说白了就是我们服务是否ok。甚至于还需要对业务做监控。比如说我们这是一个电商站点,有那么某一些商品,被这个交易次数最多的商品排在第一位的第二位的第三位的分别是谁?那么每秒钟的成交次数成交数有多少个,等等等这些在业务这边我也有可能需要做了解,做实时反馈的。所以事实上我们对于整个系统而言。我们需要了解指标很多个,在硬件级别我们可能需要了解你的各种各样的硬件资源占用率。每一台节点上他的硬件资源占用率我们都需要了解那cpu,内存,磁盘io,网络io等等,接着再向上,我们有可能还需要了解运行在此节点上的每一个服务正常与否。比如像web服务是不是ok的。另外web服务程序都启动的线程数是不是足够多,并发响应数量有多少个等等等这些数据我们也都要实时得知。有必要的情况下,有些场景中我们甚至还有可能需要对业务本身做监控。比如我们了解每秒的交易量,我们的整个交易排名等等等,这些也需要做监控,这时候我们就需要一个强大的监控系统。一直有那么一句话,一般来讲让任何一个未经监控的系统上线的行为都是耍流氓对吧。ok,当然了,事实上我们对于我们的整个系统来讲,说白了这一块,反正一个简单的道理,任何时候我们都必须让任何一个上线的主机和主机的服务,甚至是业务都要处于我们监控系统的监控之下。所以在构建了这么一个系统之后,接下来就是考量如何能够让一个系统纳入到监控范围,让他们一举一动都在我们监控范围之下,让其任何异常行为都无所遁形。所以这时候我们就需要在此基础之上给他部署一套强大监控系统。不过想象一下,谁来监控中纪委呢?这一点应该要了解的,其实我们监控系统自己也要监控,也要费纳入监控系统之下也意味着我们要自己监控自己,对吧。 我们还要想象一下一旦我们监控系统发现某一个节点或者某一个角色内的某一台主机出现故障的话,我们要及时去修复他,对不对。那有可能如果没有热冗余,比如我们只是在这里做了负载均衡前,端能够做健康状态检测,一台主机出现故障,那无非就是新的用户请求将不再发送给这个节点而已,对不对,那这个节点虽然说不用在发送给他了,但是考虑到我们系统的容量规划我们需要及早的将其下线修复并重新上线的。因此,监控系统能够给我们报告出错误来。那接下来我们如何能够快速的让这样的一个系统给修复并重新上线。之前其实讲到过,我们可以为当前系统内的每一台主机都定义一个目标角色。那一旦一台主机出现故障时,我们立即找一个好的主机。这个主机至于说系统什么从哪来的什么,我们先不讨论。你只需要知道这台主机是好的,只是可能上并没有安装相应的服务,并没有配置其能够在那个节点工作,我们只需要把这台主机找进来,把他的ip地址配置成为此前这台主机ip然后扔到这个网络上来,那这个时候我们有一个服务器能够负责确保当前网络内的每一个主机都能处于其目标角色。比如这个新来的家伙,我们一监控发现,或者一探测发现他并没有处于其应该所处于的目标角色,就强制要求他装程序包提供配置文件启动服务。然后ok了。那所以这些工具能够极大的帮助我们或者减轻我们的运维工作量,提升运维工作效率的。而这两套系统,第一个这个系统,就叫做监控系统。那另外一套系统就是我们的专用的运维工具。

        OS Provisioning(PXE,Cobbler)
              第一个层次我们要确保操作系统能安装,能自动化安装完成,第一个我们叫做os Provisioning(PXE,Cobbler),此前曾经讲过,能够实现系统自动安装的叫做pxe,但是pxe通常简单的讲一套pxe环境只能应用一个系统,那如果我们让一套pxe系统能够应用多个环境,在pxe基础之上再做一层封装,这个系统我们通常叫做Cobbler

        OS Configuration(ansible,puppet,saltstack,chef,cfengine)
              那系统有了之后,接下来还需要确保我们系统能装上相应的程序包,提供相应的文件创建相应的用户,确保相应的服务能启动起来的这种系统,这种叫做OS Configuration,这些工具中有像ansible就是其中一个,只不过ansible的功能可能更强大,那还有puppet,还有saltstack,还有chef,还有cfengine。当然cfengine现在可能很少见了,而别的都各有各自的市场占有率。其实目前对我们而言最稳定的,用的最多的当属puppet就是工业级很成熟的一套解决方案。

        Command and Control(func,ansible,fabric)
              再接着,下一步要实现Command and Control(任务执行),这套工具有func一个统一的控制工具控制器,但是func很重,因为func也需要每一个被管控节点都要安装agent。好的,那ansible其实也是这套工具中的一个,他也能够批量执行应用的,对吧。那另外还有像fabric。

  puppet:IT基础设施自动化管理工具(puppet:原意是牵线木偶)
        整个生命周期:
              provisioning
              configuration
              orchestration
              reporting

  www.puppetlabs.org(www.puppetlabs.com)

        作者: Luke Kanies, PuppetLabs

              2005, 0.2 --> 0.24.x --> 0.25.x --> 8.26.x(2.6.x) --> 2.7.x --> 3.0

                    puppet所能够实现的功能,此前讲到的ansible几乎都能实现。所以他跟ansible是一个类似的工具,但是他比ansible功能要强大。而且所实现支持的规模更为庞大。比如我们使用puppet的在一个网络内,管理个几千个节点是很轻松的,而且puppet自己有守护进程,每隔固定周期会推送相关配置。因此,用户无需过多手动参与。ansible是无agent的,而且ansible默认情况之下,他需要用户手动去触发。但puppet多数情况下,无需用户手动触发。那一般来讲,puppet官方给的定义叫IT基础设施自动化管理工具,就这么一个组件,它能管理整个IT设施的整个生命周期。大体上能管理哪些层次呢?第一,provisioning目前来讲,puppet在装系统方面还不是特别强大,但是已经在开发相关组件,他也能借助某些功能来完成系统安装。更何况现在我们的IT环境多数都是虚拟化,甚至还像docker容器化发展,那到了这个层次以后系统安装基本就不用了,我们只要有相应的模板文件,相应的容器,我们就能迅速启动起一个符合我们需要的基础的系统来,在这些基础之上,我们去做配置就要容易的多。那因此更重要的功能就表现在第二层次就是configuration他是puppet最强大的功能所在。另外他不但要实现configuration还要实现第三个功能叫orchestration(编排)或者叫联动,你怎么理解都行。简单来讲,我们要想实现某种操作,他可能需要大量的主机,同时按照固定步调完成某种任务。在必要的情况下,有可能需要编排在整个系统中,至少说在整个系统的某一个子集上要顺次的,而且是按照某种固定的节奏来完成某种功能的。那这个我们通常的说叫orchestration。另外还要明白,一旦这些操作都完成了,我们的puppet或者是这种所谓的运维工具,就应该周期性的自动的向管理员去报告他今天做了什么事情,完成了哪些操作,以便让管理员随时了解情况,所以这个叫做reporting。而一般来讲,IT基础设施自动化管理,管理它的生命周期大体上就有这四个维度。从最初开始到整个内容结束,大体上就称为叫provisioning,configuration,orchestration,reporting这四个维度,puppet他的官网站点www.puppetlabs.org,puppet作者,其实他在上个世纪90年代时,本身就是一个系统管理员。他需要一个能够满足像现puppet所实现的某种功能子集的一个工具。市面上没有特别趁手的工具,所以决定自己研发一套工具出来,这个作者叫Luke Kanies。他研发了工具之后,发现这个工具功能越来越丰富,越来越完善了。于是干脆就自己成立一家公司,专门来维护这些工具叫PuppetLabs这家公司,叫puppet实验室。好的,大体上puppet真正公开,并且以开源形式进行发布的时候,这个时间大概已经到了2005年前后。那时候版本是0.2的版本第1版。后来在0.2的基础之上不断的引进下一个公开发行的比较完整的版本叫0.24,在后来就发展到了0.25的版本或者叫分支,在接着就到了0.26,不过到0.26以后,这个作者觉得这个玩法不好玩了是吧,于是摇身一变,这个版本就叫做2.6,那所以在后一个版本就叫做2.7。不过2.7之后的版本就已经到了3.0了,2.这个分支就不在维护了。其实2.7和2.6的区别还是很大的,3.0和2.7的区别倒不是特别大。

  puppet:agent
        master/agent
              master:puppet server
              agent:
                    真正执行相应管理操作的核心部件;周期性地去master请求与自己相关的配置:
                          puppet是一个有agent工作模式,这就意味着在每一个节点上,每一个被管理节点上都必须安装专门的agent,那有没有想象过,在agent安装之前,也就意味着他是不受puppet服务端管理的,不受puppet server管理的。那因此agent谁来去安装,谁来负责实现这个管理功能的。ansible好,这也算是一种不错的思路,用ansible远程的去配置每一台主机先安装上puppet agent。然后并配置好,而且能够连入到puppet服务器端。或者接受puppet server管理,随后他就能够在puppet server指挥下完成某些操作,对不对。但是这种方式其实并不是特别理想。我们还有没有别的方式呢?现在我们很多操作系统的提供其实都是虚拟化和容器化的。那我们在事先制作模板时把agent都已经直接制作在系统当中其实就可以了。在系统配置时就直接进行分发。那所以系统启动起来以后就直接在网络中去找我们给他指定好的那一个puppet server。只要能找到,那就直接把自己自动自发的纳入到他的这个管理当中去了。那所以站在这个角度来讲,puppet本身它的工作模式是master/agent模型,master端我们通常就把它称之为puppet server,而agent端它的主要作用就是,接受puppet server所发来的每一个指令在本地完成执行以后,并且将结果报告给server端,所以agent端是代表master派出在各地完成相应的各地的管理功能,可以想象一下puppet server就像是吾皇万岁,对吧。而后吾皇万岁就派出各地的老大,比如各地的郡守,能够守护一方。所以说像各地的郡守,那要去负责各地本身完完整整的管理工作,他只需要把本地的完整的管理工作的结果报告给吾皇万岁谁就可以了。当然光报告还不够,要上税是吧,这是最关键的所以你懂得对吧。所以就这样一个概念。那因此主机节点这里周期性的发送报告,确保他们在自己的管辖范围内工作。而每一个接收到指令以后,agent他负责在本地完成相应的管理功能。这样的好处在什么地方?他比起ansible来讲好处在什么地方?ansible管理每一个主机时由于ansible对应节点是无agent,所以基于ssh协议最为常见,对吧,那因此他们连接时就需要一个拥有管理账号权限的用户,而且要远程登录认证以后才能允许它管理操作,所以为了安全起见。我们一般不建议ansible直接使用管理员直接远程,用管理员账号,直接远程的。因为这样是相当的危险。但是ansible大多数管理工作其实就要求有管理权限才能完成,对吧,那这个时候怎么办?那也就意味着ansible每一个被管控节点上,首先得有一个支持sudo的用户,不过要明白,既然你的ansible能sudo,那其他人如果能拿到这个账号是不是照样能sudo。所以这种方式是相当危险的。即便用了sudo他也一样不安全。      像这种有agent就不用这样子了,我们只需要服务端和客户端,二者之间能够使用自己的逻辑完成认证。说白了就是我们在每一个被管控主机上装上这个agent让这个agent在本地拥有权限,而我们去认证agent时,或者agent认证这个管控节点或者吾皇的时候,那你只要把玉玺拿出来就可以了。大概就这个道理,所以这个时候站在这个角度来讲,这个主机二者之间使用自己私有的认证机制,能认证通过就行,而不是认证操作系统,比如说二者事先存一个预共享密钥,或者是干脆双方使用证书认证。我自己建一个私有CA,给服务器端发一个证书,然后给客户端发一个证书。那各自向对方出示证书,而且都是各自都共同认可的第三方或者是我们自建的CA所签发的证书。所以二者,就天龙盖地虎,宝塔镇河妖了,对上了对吧,那所以后续的工作就进行了。无需任何用户账号就能完成了。因此现在人们认为有agent这种方式反而是比较安全的机制。而且它确确实实是较为或者是更为安全的一种方式。 对于我们的agent节点来讲,他应该是能够拥有管理权限,在本地真正执行,所谓的管控操作的,server端仅仅是发送了管控指令。那因此agent端应该具有管理功能。这就是真正拥有管理功能的。所以想象一下,有没有这种可能性呢,比如皇帝派出了各种诸侯国,最后某一诸侯国不受管辖独立了。可不可以的?当然从道义上来讲是不可以的,对吧。从事实上来讲呢,那当然是可以的,为什么不可以,他在一国内啥都能玩,这是没什么问题的。所以事实上对于puppet来讲,那我们要想学习puppet使用,尤其是puppet各种核心概念的应用,你无需在master/agent模型上实现每一个agent在本地自我可以实现几乎所有功能。agent端只不过能够接受master统一指挥和调配罢了。所以我们将来要学习puppet你只用先去学agent自己的工作模式。不用让它工作在master/agent模型,使用手动 去触发功能,这跟ansible一样只不过我们触发都是本地,好了,当本地的这种触发配置所有东西都完成了,把他的配置操作给他扔到master上,让master往下分发,那于是所有功能都能完成了。那因此对于puppet而言,它的重心是在agent端的。因此agent端是真正执行相应管理操作的核心部件;但是他只能管理单个节点。以此master就是用来协调并管理多个节点,它是一个统一指挥者。而agent端它的工作方式,他就是自己周期性的去master端请求与自己相关的配置,到本地来,并根据对方所提出来的结果,或者对方给我们反馈的结果来确保本地的配置一定符合master端所给定的要求。这就是puppet基本的工作思路。
        
  puppet的工作模式:
        声明性,基于模型
              定义:使用puppet配置语言定义基础配置信息
              模拟:模拟测试运行:
              强制:强制当前与定义的目标状态保持一致:
              报告:通过puppet api将执行结果发送给接收者:
                    puppet工作模式,它其实主要是基于一个声明性的配置语言,它有自己的专用的叫声明性的配置语言,而且是基于模型来进行构建的配置机制。简单来讲,我们只需要知道每一个agent所在那台主机他应该做哪些操作呢?我们需要在master上定义好这个主机,大体上要通过哪些个操作确保这个主机一定处于我们所谓所期望的目标状态,就ok。那这个所谓对于指挥agent端完成工作的那么一个配置我们就通常称作一个声明性的基于模型的配置语言。说白了就是puppet有puppet的自己的配置编程语言,puppet是基于ruby语言所研发,因此他的配置模型在一定程度上是ruby语言的子集,我们要去写puppet配置,大体上它也能支持像ansible中学习的条件判断,循环甚至还支持变量,变量还有各种各样的数据类型, 甚至还支持,正则表达式等等等。所以它的配置完完整整是一个编程语言,或者是一个简装版的编程语言,这个配置 其实就是指明,每一个主机的某一个服务或者某一个特性要处于什么状态。比如我们要确保http程序包要装上,那怎么去搞呢?自己根据他的编程语言写出来,那怎么定义一http主机程序包要要装上服务要启动要有配置文件,其实ansible就是参考puppet实现方式来做的,二者的工作思路几乎是一样,至少说在他的编写模式上,很多地方,核心代码几乎都是相似的。这是他比ansible听起来要复杂一点的地方。这是他的所谓的叫声明性的基于模型的配置语言。而大体上我们要能够让puppet工作起来主要有四个步骤来实现。第一个步骤要去定义,说白了master端要指明每一个agent端,要给每一个agent端提供配置。那这个配置显然不是puppet agent自己来的。而是作为管理员,我们去提供的。那这个步骤我称作叫定义,说白了就是使用puppet配置语言定义基础配置信息。为每一个主机都要提供配置。当然了如果主机非常多,而有些主机的角色是一样的。比如说web server有200台,你没必要为200台一一去分别编写。我们只需要编写一个通用的跟ansible中的role一样,然后套这两个在这200台主机上就可以了,这是第一个。      第二个叫模拟,什么叫模拟?你定义完了agent端就要拿来与自己相关的定义在本地运行了,但是在运行的时候如果出现错误怎么办呢?那因此,为了避免这样的问题,他会首先在本地做模拟执行,先自己干跑一遍。但这个跑的过程并不真正应用,只是测试性的跑一遍。所以我们就把它称作模拟emulation。而测试跑一遍之后没问题,可以确保我们当前agent这台主机能够正常,切换至我们在master端所谓为其定义的目标状态的话。那于是下一步就真正执行了,的本地走完所有配置。那走完之后应该告诉master端,我们这边已经ok了,所以最后一步叫报告,所以这是puppet大体上的工作机制,定义,模拟,执行和向master端发送报告。所以第一个定义我们称之为叫做使用puppet语言来定义资源的状态。在puppet当中我们通常去定义每一个目标,都是通过所谓叫做资源来进行定义的这种资源,跟此前讲的集群中的资源是两回事。你可以想象成就是ansible中的模块,在puppet中我们通常把它称作叫资源,第二个叫模拟,根据资源关系图puppet可以模拟部署(在本地无损运行测试代码),如果运行没有任何问题。接着第三步叫强制,我们可以认为叫执行。这种强制的结果就是比对客户端主机的状态和定义的状态二者是不是一致,如果不一致我们就强制agent端与定义的状态完全的保持一致,而且这个过程是自动强制执行的。第四个我们叫发送报告,通过puppet API将日志发送给第三方监控工具,可以通过一些比较直观的图形化的工具予以展示.dashboard是puppet自带的,foreman是一个第三方工具,dashboard过于丑陋,特别丑陋,这个一般我们用的并不是特别多。

  puppet有三个层次:
        配置语言
        事务层
        资源抽象层
              资源类型:列如用户、组、文件、服务、cron任务等等:
              属性及状态 与 其实现方式分离: 
              期望状态
                    在prppet中最核心的像模拟,强制,报告等等,都有它的agent来完成的。而做为管理员来讲,我们需要做的工作集中在哪里。第一步,使用puppet配置语言来定义基础配置信息的。那接下来就来聊一下puppet配置语言。其实我们要想了解puppet配置语言先来说一说puppet的层次。puppet其实有三个层次,从逻辑结构上来讲,puppet一共有3个层次。第一层次我们称作叫Resource Abstraction Layer(资源抽象层)资源抽象层说白了就是把主机上每一个可被管理对象都抽象成为资源,或者都定义为资源,其实这个事儿很容易理解。比如在每一个被管理主机上,无非就是能被管理的对象,像用户账号啊,程序包啊,服务啊什么之类的,对不对。那这一切就是被抽象出来的对象,就是这概念。所以资源抽象层就是把每一个被管控对象以类别的方式抽象出资源的。那为什么还是要特别说这个抽象层的概念,举个例子,各位就明白什么叫抽象层了。比如说在windows之上用户账号的创建方式。和linux之上用户的账号创建方式应该是不一样的,还有程序包,windows程序包安装,和linux程序包安装,甚至说对linux有rpm包安装,他还有dep包安装。有多种程序包,每种程序包它的安装方式都是不尽相同的。容易理解吧,那puppet为什么把它做成 一个资源抽象层呢?还有它的主要作用或者主要目的就想把具体的实现方式,与功能给他剥离开来,比如我们想创建用户,使用配置语言定义时说创建用户就行了。而这个创建用户的指令发送给puppet agent,如果假设这个agent工作在windows之上,那这个agent就能够调用在windows之上能够完成系统创建方式来创建用户。如果发送是给linux 上的agent就能够利用Linux上的主机来创建用户的方式进行创建。所以他把实现和目标给分离开来了,用户无需关心他是怎么具体的实现。你只需要告诉他创建用户就ok,所以说这叫做资源抽象层。      向上层还有一个叫做事务层Transactional Layer,为什么叫事务层呢?因为资源彼此之间有可能会有依赖关系的,比如说启动httpd服务,如果httpd程序包都没装,和谈启动。那因此它是应该有先后关系,是有依赖关系的。还有再比如说如果httpd程序包的配置文件发生改变了,那这个服务要不要重启啊,你应该触发它重新启动。那这一切其实我们就是所谓通过抽象层来实现的功能,在向上一层叫Configuration Language。只有把这一种资源抽象出来,而且能够有事务层保证其事务,所以这个配置语言就完整了。我们就可以通过这个简单的语言来定义,来借助于事务的功能,来借助于资源抽象层所抽象出的每一个资源基本属性来配置。      对于资源抽象层而言,我们来具体说一说,大体上主要 功能就是抽象资源的,而抽象资源对于puppet来讲,主要我们可以通过三个维度来理解他。第一个称作叫资源类型,每一种类型资源代表了一类可被管控实体。就像刚刚所说的,例如像用户账号,组,文件,服务,甚至于cron任务等等,这一切都是可被管理的对象,我们都把它称之为不同类型的资源,这是第一个。第二个,每一种资源都应该有他的属性,比如说在ansible通过模块来创建一个用户,那应该给他指定用户名,id号,所属组等等。这一切我们都称为叫一个资源的属性,我们一旦给一个属性特定值的话。那这个对应的对象就呼之欲出了,我们就指明了。比如就创建一个用户,一个用户的属性应该有哪些?有用户名,有id号,有所属组,于是我们给他赋值了,用户名叫某某某,id号是某某某,所属组是某某某,就是把它具体化出来,那于是我们就可以去定义这么一个资源了,所以说我们要想能够让一个资源可被实现出来,那我们可以定出一个资源有很多属性。这个属性可以给他指明配置值。但是对于puppet而言,他所实现的功能当中,有一个特性叫属性及状态与其实现方式是分离的。说白了就是刚才说过了,像用户账号到底是如何创建,具体是如何实现的,不同系统肯定是不一样的。我们只需要指定属性,你不用关心他的实现是什么。因为puppet agent会在底层自我来完成这个功能,这是第二个。第三个每一个 资源我们还都应该定义其期望状态的,比如服务是否启动等等。      这就是puppet他的三个层次,尤其是资源抽象层,这里资源能够抽象出这种结果来。我们在这种基础之上定义出事务,或者能够让资源和资源之间产生依赖关系,能定义次序关系。那于是我们在给他提供一个配置语言,通过这个配置语言借助于事务,我们就可以定义各种各样的资源了。      说到这儿就应明白puppet核心组件其实就是资源

  puppet的核心组件:资源
        资源清单: manifests
        资源清单及清单中的资源定义的所依赖文件、模板等数据按特定结构组织起即为"模块"
              就像此前使用ansible一样。我们可以在命令行中直接给出來命令运行。但是我们在命令行中给出每一次时都得写,为了能够便捷多次运行怎么办呢?可以写一个yaml配置文件,对于puppet而言也是如此。我们可以在命令行中实现,当然是最好的方式,是写一个配置文件只不过在puppet当中,他的配置文件不叫做配置文件,而叫做资源清单,什么是清单?就是列表嘛。那我们打算应用哪些资源,你定义了N个资源,把每个资源都给他一一列出来,放在一个文件中,这个文件就称为叫资源清单manifests。即所谓清单之一,所以资源是定义在清单中的资源清单及清单中所定义的资源以所依赖的其他数据按特定结构组织起来以后,可以形成在ansible中叫角色。但是在puppet中他不叫角色,反而叫模块。这两个要注意,所以puppet模块概念相当于ansible中角色的概念。而puppet中资源的概念相当于ansible中模块的概念。

              在puppet当中,他到底是如何用到资源以及如何资源组成模块。我们master端和agent端是如何进行交互的。对于puppet而言,manifest叫清单,每一个清单都定义了,一个和多个资源。那么一个agent上所用到的清单可能不止一个。为什么呢?第一个清单里面定义的是这个主机要创建三个用户,六个组这是1个清单,第二,这个主机上要装10个程序包,第三,第3个清单里面定义了我们要启动6个服务。所以我们把它分别定义在不同的清单中了,因此这些清单文件可能是有多个适用一个主机的。当然你也可以指定只有一个,这无所谓,看你个人喜好。但是这些清单要想在客户端或者agent端要想执行,首先要做编译。因为他们事实上是在Ruby语言所写的puppet,所以整个puppet的内部的这些代码也是基于ruby模型运行的,而ruby语言的运行是需要在ruby虚拟机上的。这根Python PHP Java没有什么太大的区别,只不过他用的是ruby虚拟机。所以说像Java语言先编译成字节码, OK,那这里也是一样的道理。所以我们需要把manifest中所编写的各资源的定义,给他编译成字节码,只不过我们不把它称之为字节码,而称之为叫做catalog叫伪代码。形成catalog以后,这个其实就接近二进制的形式了,我们可以把这个catalog在本地进行执行,而执行的过程称作应用,就要做apply应用在本地的过程。应用过程大概需要有两步组成,第一步叫状态查询,第二步叫执行目标状态。状态查询,简单来讲比如在manifest清单中定义了要装8个程序包,但本地已经装过6个了,你再需要装两个,是不是就OK了,就这意思,所以叫状态查询,看我们已经满足了其清单中定义的功能中的哪些,他缺少哪些,需要改变哪些,那这个我们需要在本地先通过状态查询来完成。一旦状态查询完成了,我们就可以确保可以做查漏补缺了,那于是第二步执行目标状态,执行最终应用,然后要报告。当然了,如果你只是本地应用的话,他就没有报告一说了。这是在本地管理自我应用的。      如果我们工作在master/agent模型下,这个过程就分为这样的步骤。左侧是agent端,右侧是master server端,每一个主机跟与其相关的清单都不在本地,而是在master端。每一个agent都是周期性的每隔一定时间到server端去请求与自己相关的清单,那所以向master server端发送请求catalog,去请求自己相关的catalog,在请求的同时还会发送自己的主机名(主机名至关重要,puppet是严重依赖于主机名的)和facts(facts是每个主题都会报告自己的各种各样的属性,比如我们主机名(主机名至关重要,puppet是严重依赖于主机名的), IP地址,cpu颗数,我们服务器端需要根据这些信息,因为服务器端可能引入这些变量在模板文件中,可以把这些变量内容替换相应的值已达到具体应用,实现一些更为特定的功能实现所以说每一个主题在请求自己的catalog的时候会向主机先发送自己的主机名(主机名至关重要,puppet是严重依赖于主机名的)和各种各样的facts。)服务器端收到请求以后要查询请求者的站点清单。所谓站点清单与清单不同的地方在于每一个站点清单就相当为每一个主机所定的清单,所以一个站点相当于一个主机。那我们在server这里定义的清单可能有1万个,其中第1个主机应用2000个,第2个主机应用30个,那么第1个主机用的是哪2000个?第2个主机应用的是哪30个?我们要给它归一下类说明一下。那因此我们定义的清单可能有1万个,然后第1个主机从这1万个当中挑出来50个定义在这个主机专用的列表中,所以这个主机专用列表调用了哪些清单,这个清单我们就叫做站点清单。      所以master端会查询请求者相关的站点清单查询完以后,从我本地的各清单里边找出这个站点清单定义的与这个主机相关的所有清单,把这些manifests都找出来,在master端完成编译,把编译结果直接发给agent。那这样的好处在什么地方?请问我们每一个agent能看到源代码吗?不能,就像我们访问网站一样,你访问的是微博的站点是没错,但微博站点上肯定是一些动态网站的代码所以实现了,我们当然看不到他的动态代码是什么,好处在于至少不可能把整个网站拿过来。这里也是呀,你自己好不容易开发那些代码,别人随便应用就拿走了。我们肯定不允许,那因此我们发送给对方,压根就是编译好的结果。      你可以想象,我们服务器端就是一个应用程序服务器把代码运行过了,运行好了,把运行结果发过来了,这个运行结果当中,它的编译过程包括像变量,做成变量替换,而代码运行完并编译成二进制,结果这时候就成了catalog了。所以服务端发送给客户端的直接是一个catalog,而不是源代码。      那客户端拿到自己相关的catalog以后,于是在本地做状态查询,做(执行目标状态)强制应用,把应用结果再报告给master 。      所以这是主从模型下,二者交互过程。如果不是组成模型而是单机模型的话,每一台主机都在本地直接运行,自己本地直接编写源代码,直接在本地编译,在本地运行也是可以的。也就意味着说master 能编译,agent也能编译。如果我们不打算让他工作在master/agent模型下,让每一个agent都自我独立工作也是可以的。但我们一旦工作在主从模式下,这里的变异过程将不再agent上实现,而是在master上实现的。这就是puppet它的两种不同模式下大体工作流程。   

  How does Puppet work?(木偶如何工作?)
        接下来我们看一下puppet的到底是如何工作的。简单来讲,对于puppet的而言,我们可以把我们自己定义的各种各样的资源放在清单中,而清单中的资源所依赖的各外部数据文件也组织起来,形成特定的组织结构,叫做模块,模块可以有多个。 比如假设我们有n个模块,第1个模块是做配置的,第2个模块操作系统管理,第3个模块是应用程序管理的。我们有三台主机,那么这三台主机它有可能运用哪些模块?那我们按需配置好对第1个主机应用哪些模块对第2个主机用哪些模块,对于第3个主机应用哪些模块,但是不管怎么讲,我们将多个模块拼凑起来,专用于某一个特定主机的,这就需要把他们放在一个单独的文件中。来说明只用于某一个单独的主机这个我们就称作为站点清单。所以每一个主机到master端请求与自己相关的配置的时候,就是把请求发送给master端,发送时还要报告自己的facts,master端收到以后去对比看看这个主机的名称是什么,并找到与这个主机名(主机名至关重要,puppet是严重依赖于主机名的)称相对应的那个站点清单文件,站点清单里面定义了这个主机上应该有哪些清单,然后master端从库里面都调出来,然后进行编译,就是这么一个过程,这就是puppet的工作模式。    之前说过puppet各种资源的实现,那它的属性及其状态的定义与其实现方式是分离的。因此他支持。redhat,sun,Ubuntu,suse,mac等等市面上常见的各种unix life操作系统都能实现。你只需要告诉他创建用户,无论这个锁定的主机到底是哪儿都无所谓。所以我们在定义资源时,你不用考虑底层操作系统是什么。实现方式是什么?只需要定义好这些资源是哪种类型的,叫什么名字,有哪些属性,各个属性值是什么就ok了

         puppet master与puppet agent彼此之间是通过什么协议进行的呢?像ansible与ansible每一个被管理节点是通过shh协议进行的。那puppet master与puppet agent之间是通过https协议进行的。这样至少保证配置不会被别人随便拿走。其实他还可以让双方基于证书直接进行认证。我们只需要给客户端配置一证书跟 puppet master端配置一证书。而且做完签署,双方共同相信,认可,信任这一CA就可以实现了。但是真正实现起来要比我们所描述的要简单。因为对于puppet master而言,首先他自己就是一个CA,他自带了一个私有CA,他自己给每一个client(客户端)发证。当然或者说每一个client(客户端)来连接master端时他会首先在自己本地生成一个自签的证书请求,注意是自动的,并发送给master端的那个私有CA。那马上就可以就想到了,你这样来的话,那认证还有什么意义?所以master端要不要给他签就成了非常关键的一步了,签署了就可以一块玩耍了,不签署client(客户端)是请求不到任何资源的。那这样又出来另外一样问题了,假如说你的网络中被管理的节点有6000个,每6000个节点同时启动你的master端就收到了6000个签署请求。那我怎么确认哪一个是合法的哪一个是不合法的。没有特别好的办法,自己在那儿一个一个检查的看,看完了第一个没问题签署,看完了第二个没问题签署,以此类推,因为服务器端并不知道哪个是可信的,哪个是不可信的。你才知道服务器并不知道。那假设我们认为,我们的内网中,是没有任何杂质的。我们可以让master的端,自动签署,只要有请求那就签署,不过这有可能会带来安全风险。那鱼是思想更省事儿,还是更想安全。你的选择你做主,你的责任你负责,大体上他其实就是这么一个工作模式。

  Define Reusable Configuration Modules(定义可重用的配置模块)
        角色的功能是为了实现代码重用的,这才是他最核心的目标,可以把它应用到n个主机上去,类似于我们编程中的函数。当然了,他也是一个自组织自管理的这个代码模块或者代码结构。同样的我们在puppet中定义模块的主要作用,与ansible定义角色的作用是一样的。主要是为了保证代码重用。比如说我们一共定义了这样几种模块,第一个是database,第二个web server,第三个是app server,第四个是security(安全),那么很显然,每一个主机都需要注意到安全问题。假如说有6个节点每一个都应用了security模块,而且每一个节点我们都把它配置为web server,如果我们只希望其中4台主机做database server于是这4个主机,都用了database模块。只有两个主机用来实现app server就可以了。所以模块化的结果使得我们此前定义的代码可被重用。而且由于每一个模块定义时,它的各种文件都是自组织的,所以不在于依赖外部条件就能进行工作。

安装并使用puppet

  agent:puppet,facter
       一般而言我们agent端其实只需要安装,第一puppet,第二facter,安装facter是因为我们agent端才真正需要facter,因为它每一次去连接master端就需要先去报告自己的各facter,这是安装agent端所需要的程序包

  master:puppet-server
        对于master端而言,它将来只需要按照puppet-server,不过他要管控自己的话,也需要装上puppet,facter

  要使用RPM包的话,还要确保跟你的操作系统的版本相匹配,对于各版本操作系统,官方打包好的程序文件都会有。对于epel源当中已经直接收录了puppet,而且版本还是相当高的。而puppet是使用ruby语言研发的,所以要确保的ruby环境是ok的,centos6和centos7他们所提供的ruby的环境可能版本并不一样。因此我们尽量不要跨版本,不要跨操作系统版本去装程序包,至少对于puppet而言,适用el6的安装el6版本的,适用el7的安装el7版本的就ok
# 使用阿里云的centos7的epel源(将来真正在生产环境中使用时,并不建议使用使用最新的版本,可以直接使用epel源中的所收录的那个版本,因为那个版本至少是通过epel组织所认证以后较为稳定的版本,不要追求最新,追求最稳定才是最重要的)
[root@localhost ~]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

# 查看所有仓库
[root@localhost ~]# yum repolist

# 查看puppet包(这里是3.6.2的版本,有两个程序包一个是puppet.noarch,一个是puppet-server.noarch )
[root@localhost ~]# yum list all | grep -i "puppet"
puppet.noarch                            3.6.2-3.el7                   epel   
puppet-server.noarch                     3.6.2-3.el7                   epel 

# 安装(注意:centos7中epel源中的puppet依赖于python2的版本)(我们现在安装只用到puppet和facter就行了,puppet server目前用不着,因为这里只是简单演示单机模型下如何使用)
[root@localhost ~]# yum -y install facter puppet.noarch 

# 查看python版本
[root@localhost manifests]# python   
python
Python 2.7.5 (default, Aug  4 2017, 00:39:18) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

# 查看puppet安装生成的文件(less分页查看一下)
[root@localhost ~]# rpm -ql puppet.noarch | less
/usr/lib/systemd/system/puppet.service
/usr/lib/systemd/system/puppetagent.service
            这两个是puppet可以作为服务器运行起来的。为什么时候会作为服务运行呢?他工作在master/agent的模型下需要周期性的到服务端去请求与自己相关配置时需要运行的,不然他怎么去周期性去请求,对不对。
/usr/bin/puppet      (那对于puppet而言有个专门的工具叫puppet,他的程序就叫做puppet)

/etc/puppet      (配置文件在/etc/puppet下)
/etc/puppet/puppet.conf      (主配置文件)

# 查看帮助(一般而言,像ruby,python或者php,java这种所谓的运行在虚拟机上的编程语言,他们的帮助信息的获取很有可能方式是各不相同的,-h --help可能未必支持,他可能有专门的子命令叫做help,所以这个时候help是一个完整的子命令)
[root@localhost ~]# puppet help
Usage: puppet <subcommand> [options] <action> [options]      (命令的用法格式)
      我们要在本地使用的话,就要用puppet命令了,而puppet命令有puppet后面跟上<subcommand>各种子命令,以子命令的[options]专用选项也可能是通用选项,而后跟上这个子命令后面要执行的<action>共同组成,这里子命令<subcommand>是必选的,而后<action>是必给的
Available subcommands:(可用子命令)
agent             The puppet agent daemon      (人偶代理守护程序。(专门用来puppet agent这个一个守护进程的))
apply             Apply Puppet manifests locally      (在本地应用人偶清单。(用来在本地运行清单文件的。我们此前说过,我们可以在本地直接运行清单直接编译))
ca                Local Puppet Certificate Authority management.      (本地人偶证书颁发机构管理。(管理本地ca的))
catalog           Compile, save, view, and convert catalogs.      (编译,保存,查看和转换目录。(编译,保存,查看,转换catalog文件))
cert              Manage certificates and requests      (管理证书和请求(管理证书的和证书签发请求))
certificate       Provide access to the CA for certificate management.      (提供对CA的访问以进行证书管理。(也是用来管理证书的))
config            Interact with Puppet s settings.      (与Puppet的设置进行交互。(用来交互式完成Puppet配置的))
describe          Display help about resource types.      (显示有关资源类型的帮助。(用来显示资源类型的帮助))

## 显示子命令帮助(对于某一个子命令来讲,他可能会有很多的<action>,比如说使用describe这一个子命令,直接回车,对应的他会显示出describe子命令的帮助,而这个子命令它定义的action无非就是像file,user等等这些资源类型,也就以为这对于describe而言它的action其实有默认action我们不需要专门去指明什么action了。这反而是一个比较独特的命令。)
[root@localhost ~]# puppet describe
OPTIONS
-------
* --help:
  Print this help text

* --providers:
  Describe providers in detail for each type

* --list:
  List all types      (列出所有支持的类型)

* --meta:
  List all metaparameters

* --short:
  List only parameters without detail



EXAMPLE
-------
    $ puppet describe --list
    $ puppet describe file --providers      (了解某个资源的具体使用,describe 后面跟上类型就ok,比如puppet describe package)
    $ puppet describe user -s -m

列出所有支持的所有资源类型

  它好像没有ansible那么丰富,因为他很抽象,像ansible不同的系统上的不同的安装程序文件,它使用方式是不一样的。但是puppet要安装程序包,只需要指定包名就可以了。像package就ok。所以它抽象程度更高级。      他的资源类型里边大体可以分为最常用资源类型,次常用资源类型和不长用资源类型,我们来讲,我们也无需每一个都细细的去描述。只需要简单的讲一讲,最常用的资源类型就ok
[root@localhost ~]# puppet describe -l
#或
[root@localhost ~]# puppet describe --list

列出某一资源的使用格式

  比如package资源类型,这里大体上有非常详细的使用帮助,比ansible帮助详细多了,而且还有示例
[root@localhost ~]# puppet describe package
package
=======
      我们这个一个package是用来管理程序包的

Parameters
----------
      大体上它有很多的参数,我们也称作叫属性,这些属性里面有些是必选属性, 有些是可选属性
- **install_options**      (你的安装选项)
Requires features install_options.      (需要功能install_options。)
- **name**      (程序包的名字)
- **package_settings**      (程序包自己的设定)
- **platform**      (指明适用那种平台的)

Providers
---------
     (事实证明需要指明安装类型。) 能安装程序包的提供者有哪些,所以它是把一个资源的实现,和属性状态是分离的,我们只需要指明了装程序包,叫什么名,它会自动判断操作系统类型是什么,并利用这个操作系统自身上所应该有的所谓包管理器来完成包管理的,这是它与ansible所不同的地方,像ansible我们用的时候,必需要指明这是yum,这里我们是无需指的,给他的是rpm包他就给你装rpm包,你给的不是rpm包它会根据操作系统,比如centos就使用yum去装,所以说他的抽象程度更高就着道理
    aix, appdmg, apple, apt, aptitude, aptrpm, blastwave, dpkg, fink,
    freebsd, gem, hpux, macports, msi, nim, openbsd, opkg, pacman, pip, pkg,
    pkgdmg, pkgin, pkgutil, portage, ports, portupgrade, rpm, rug, sun,
    sunfreeware, up2date, urpmi, windows, yum, zypper

定义资源

  要想使用资源要先去定义它。puppet在做资源抽象时,主要通过三个维度来完成的。

        第一个维度,我们称之为叫 相似的资源被抽象成同一种资源“类型”,如程序包资源、用户资源及服务资源等;
        
        第二个维度,将资源属性或状态的描述与其实现方式剥离开来,如仅说明安装一个程序包而不关心其具体是通过yum,pkgadd, ports或是其它方式实现;

        第二个维度,仅描述资源的目标状态(比如是安装还是卸载,对服务来讲是启动还是停止),也即期望其实现的结果,而不是其具体过程,如"确定nginx运行起来"而不是具体描述为“运行nginx命令将其启动起来";

              这三个也被称作puppet的资源抽象层(RAL是资源抽象层的简称)所完成的基本结构
                    RAL是由type(类型)和provider(提供者,即不同Os上的特定实现)来共同组成,我们只需要关心类型和类型的相关属性,而无需关心它的提供者。

  定义一个资源,在puppet中方法非常容易要想定义资源,只需要使用type类型名,而后使用花括号给每一个类型取一个title叫名称或者称号都可以,后面加冒号,这是固定格式,然后每一种资源类型,它的attribute就是它的属性可能有很多种,我们需要把那些必须给赋值的属性定义一个值,而指的是键值对儿的方式出现的,中间=>这个符号是等于号,加个右尖括号,可以理解为属性赋值符号,如果有多个属性,彼此间使用逗号隔开,在某一个资源的最后一个属性,最后,最后一个属性后面逗号是可有可无的,但中间的这个属性就必须后面以逗号隔开,以区别其他属性。      所以每一个资源大体上都应该有一个类型,一个title和一个属性集,共同组成title至关重要,type更重要。而type只能小写这个字符也很关键,只能使用小写字符。
        type {'title':
        attribute => value,
        }

              示例:
                    user { 'keji':                         # (user这是一个类型,里面有一个keji这是资源的title,说白了就是我们定义一个user类型的资源叫keji,而后这个对应user类型的资源有以下这么多属性)
                          ensure          => present,
                          uid             => '601',
                          gid             => '601',
                          shell           => '/bin/bash',
                          home            => '/home/magedu',
                          managehome      => true,            # (最后一个属性逗号是可有可无的。)
                      }

                    注意:1、在定义时,资源类型必须使用 小写字符;

                         2、资源名称仅是一个字符串,但要求在同一个类型中其必须唯一(着就意味着user这个类型有一个叫keji,在也不能有第二个叫keji。但是如果有个程序包,比如package叫keji是没有问题的,同一类型下是不允许重名的。)
                                例如,可以同时有名为nginx的"service"资源和"package"资源,但在"package"类型的资源中只能有一个名为"nginx"

                         3、"puppet resource"命令可用于交互式查找及修改puppet资源(这是另外一个子命令,去查找已定义的资源的,或者去修改我们曾经定义的资源的。不过在主从结构中我们用起来才比较理想。)

                          总结:type必须小写: title在同一类型下必须惟一:

  资源的特殊属性,我们在定义资源时,资源当中有这样三类特殊属性。

        第一类称作叫 Name/Namevar(叫名称变量)(Namevar是什么呢,就像我们定义的user一样,前面user这里定义的是keji,这是title,但是我们并没有定义用户名,我们只定义了一个资源他的类型是user,资源他的名称叫keji,但是我们没有定义这个用户名,这个资源的title就成了用户名。这种就叫做Namevar。)
              Most types have an attribute which identifies aresource on the target system(大多数类型的资源都有一个特殊属性,该属性标识目标系统上的资源。)

              This is referred to as the "namevar." and is oftensimply called "name"(所以这个资源通常被称作叫“ namevar”。 而且它对应的有一个属性,这个属性通常也通常就叫做name,。)

              Namevar values must be unique per resource type, withonly rare exceptions (such as exec)(每个资源类型的Namevar值必须唯一,只有极少数的例外(例如exec)所以一般来讲,我们在定义一个资源,是如果没有给name这个title就成了他的name。不过这里还需要强调的是,并不是对所有资源类型来讲,它能够作为Namevar那个属性都叫name,有的不一定。比如说对file来讲,他的Namevar可能叫file,而不叫name。对用户来讲,或对组来讲叫name是容易理解的。所以Namevar他的值必须唯一,对于每一个资源类型来讲值必须唯一。说白了就是这个title必须唯一,因此Namevar也必须唯一,二者在一定程度上指的是同一个东西。)

        第二类称作叫 Ensure(Ensure对于大多数资源来讲都都是拥有的一个属性,一个通用属性,主要是用于控制资源的存在性的。)
              ensure => file:表示存在且为一个普通文件;
              ensure => directory:表示存在且为一个目录;
              ensure => present:表示必需存在,可通用于描述上述三种;
              ensure => absent:表示必需不能有,说白就是有的话就必须给他删除了。不存在;


        第三类称作叫 Metaparameters(我们称作叫元参数,或者叫元属性都可以。元参数和元属性通常指的是哪些所有的类型几乎都支持的,用来定义资源和资源之间关系的,比如像依赖关系啊,通知啊等等,这种属性名称作叫元属性。)
              puppet就是使用有4种所谓的元参数,用来确保资源间的次序这种关系。
                                第一个叫:before
                                第二个叫:require

                                第三个叫:notify
                                第四个叫:subscribe
                                      他们两两一组,第一个和第二个一组,第三个和第四个一组。

                                      这四个元参数都以另外的其它资源或资源数组作为其值,这也称作资源引用(也就以为这before属性的值,只能是其他资源,事先定义过的其他资源。所以这里是做资源引用的。)
                                            资源引用要通过"Type ['title']"的方式进行,如User['maged'],
                                                  注意:资源引用时,其类型名的首字母要大写(Type首字母必须大写)

               Metaparameters主要有三类:
                    第一类是定义资源引用
                          资源引用要通过"Type ['title']"的方式进行,如User['maged'],
                                                  注意:资源引用时,其类型名的首字母要大写(Type首字母必须大写)
                    第二类是定义依赖关系
                          从名字上应该就能看得出来,依赖关系指的是,一种资源是依赖于另外一种资源的,所以资源之间其实有次序的概念叫次序。
                          被依赖的资源中使用: before
                          依赖其它资源的资源: require
                          ->       # 表示链式依赖
                                他们二者效果是一样的,没必要同时使用。如果要构建一个链式依赖关系,我们还可以使用箭头符号。


                          示例:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                      before => User['suse'],      # 所以一定要记得,我们引用一个别的资源时,或者说你定义的资源有可能产生依赖关系的时候,我们要确保那个被依赖的资源得事先存在。比如说group定义的组id,他是user的基本组 。所以group 这个资源,要先与user这个资源,要先于实现。就是用before,意思是我要在某个资源前面,比如在User['suse']这个资源前面,这就表示引用另外一个资源了。从而我们就定义了依赖关系。                                                         
                                }

                                user {'suse':      # 如果说我们第2个定义的user ,他所属的组为,其组为2000的组,但是如果group定义组不存在或者,group定义的组id为3000,user定义的组id为2000怎么办。创建会失败的。
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                }

                          或者:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                                    
                                }

                                user {'suse':
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                      require => Group['linux'],      # 我们也可以反过来定义在user中定义group要事先存在。定义require意思是说这个资源运行要必须保证Group这个资源类型中,linux要事先存在。这两种功能是一样的。
                                }
                                      应用一下这个资源:puppet apply -v test8.pp他一定会确保,group先运行的,这个没有任何问题,因为我们定义了require 


                          或者:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                                    
                                } ->      # 我们还有另外一种表现方式。直接说明他俩有依赖关系。这个箭头表示第1个先第2个后,就表示第1个先于第2个。这就是一种链式关系。

                                user {'suse':
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                }

                    第三类是定义通知关系
                          被依赖的资源中使用: notify
                          监听其它资源的资源: subscribe
                          ~>       # 表示链式通知


                          示例:(这里面定义的有一个package,一个service,我们知道这个service必须要依赖于package)
                          [root@localhost manifests]# mkdir -pv /root/modules/nginx/files
                          [root@localhost manifests]# cp /etc/nginx/nginx.conf /root/modules/nginx/files 
                          [root@localhost manifests]# vi /root/modules/nginx/files/nginx.conf      (修改一下,主要是以示区别。)
                          worker_processes  2;
                              server {
                                  listen       8080;

                          [root@localhost manifests]# vi test9.pp 
                                package {'nginx':
                                      ensure => latest,
                                            # 一般来讲,我们在安装一个程序包以后,能够依赖于他的默认配置文件直接运行吗?很少,那我们还可能有一个配置文件样本。
                                }

                                file {'/etc/nginx/nginx.conf':
                                      ensure => file,      # 确保他是文件
                                      source => '/root/modules/nginx/files/nginx.conf',       # suorce源,文件从哪来.可以想象一下,我们去复制这个文件到/etc/nginx/目录时.
                                      require => Package['nginx'],       # 如果你的程序包没装的话,一般而言/etc/nginx这个目录是不存在的,所以我们可以理解为,这个资源要依赖于package,不装包的话,我们复制文件没有用.所以定义require依赖于package这个资源,资源名字为nginx
                                }

                                service {'nginx':
                                      ensure => running,
                                      enable => true,
                                      hasrestart => true,
                                      hasstatus => true,
                                      restart => 'systemctl reload nginx.service',
                                      require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],      # 同样的我们也可以认为service要想启动的话,我们没有file,没有package,其实没有任何意义的,或者是没有file也没关系, 至少package 是安装上的,如果我们要定义service依赖于package 和file 两个资源,我们就要使用列表的方式给出,两个被依赖到的资源。这种就叫做属组。
                                            # 一般而言,如果file资源,配置文件发生了改变,我们应该让服务资源重启。
                                }
                                                  应用一下这个资源:puppet apply -v test9.pp,hash值发生了变化,但是我的那个服务并没有重启。因此这个require定义不了重启关系的。这就是另外两个关系的属性了 notify,subscribe这两个刚好相对应。notify第一在前资源,subscribe定义在后资源。定义在前资源就表示自己发生了改变,通知给其他资源。后资源就表示自己监控的别的资源,如果别人资源发生了改变,我就自己做什么操作,所以这叫做订阅叫subscribe,他们两个用一个就行。一般来讲如果一个资源需要通知一个资源的话就可以定义在前资源中。如果一个资源都被很多种资源所依赖的话,那就定义在后资源中。

                          或:
                                [root@localhost manifests]# systemctl stop nginx      #(停掉服务)
                                [root@localhost manifests]# vi /etc/nginx/nginx.conf      (修改回去。)
                                                        worker_processes  2;
                                                            server {
                                                                listen       80;
                                [root@localhost manifests]# vi /root/modules/nginx/files/nginx.conf      (修改一下,主要是以示区别。)
                                                        worker_processes  3;
                                                            server {
                                                                listen       8080;

                                [root@localhost manifests]# vi test9.pp 
                                      package {'nginx':
                                            ensure => latest,
                                      }

                                      file {'/etc/nginx/nginx.conf':
                                            ensure => file,
                                            source => '/root/modules/nginx/files/nginx.conf',
                                            require => Package['nginx'],
                                            notify => Service['nginx'],      # 在前面这个file发送改变了,notify发送通知给Service这个类型的资源中的叫做nginx资源
                                      }

                                      service {'nginx':
                                            ensure => running,
                                            enable => true,
                                            hasrestart => true,
                                            hasstatus => true,
                                            # restart => 'systemctl reload nginx.service',      (这个一个就没用了,reload就没办法让新监听的端口生效。就先把它注释掉。)
                                            require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
                                      }
                                                  应用一下这个资源:puppet apply -v test9.pp。这次只有文件发生了改变。Scheduling refresh of Service[nginx]通知调度Service[nginx]做refresh。使用ss -tunlp可以看到监听在8080

常用资源类型:

  user, group, file, package, service, exec(执行命令的exec), cron(定时任务 ), notify(这是通知,说白了就是显示一句话。)

常用资源类型的属性

        定义资源时可以看到指定的资源的多种可定义时使用属性。每一种资源他们说支持的属性是各不相同的。
group组资源的类型
  管理组资源
  常用属性:
        name:组名, NameVar
        gid: GID
        system: true, false
        ensure: present, absent
        members:组内成员
# 列出组资源的详细使用帮助。
[root@localhost manifests]# puppet describe group 
group
=====
组资源的主要作用是用来实现Manage groups(管理群组)。

Parameters
----------(它的常用属性有哪些)

- **allowdupe**       #(是否允许重复的GID。 默认为`false`。 有效值为“ true”,“ false”,“ yes”,“ no”。(是否允许两个组使用相同的GID号,对于一个系统来讲,用户的ID号是唯一的,组id号也是唯一的,但事实上有些特殊场景中,我们允许两个组名使用同一个ID号。但是这个对我们来讲很少用到。))
    Whether to allow duplicate GIDs. Defaults to `false`.
    Valid values are `true`, `false`, `yes`, `no`.

- **attributes**     # (在“键=值”对数组中指定组AIX属性。 需要功能manages_aix_lam。(他就叫属性。))
    Specify group AIX attributes in an array of `key=value` pairs.
    Requires features manages_aix_lam.

- **auth_membership**      # (提供者是否对组成员身份具有权威性。这些用的很少。)
    whether the provider is authoritative for group membership.

- **ensure**      # (创建或删除组。有效值为“存在”,“不存在”。(这个用的很多。))
    Create or remove the group.
Valid values are `present`, `absent`. 

- **gid**
这是group ID


- **members**      #(小组成员。 对于组成员身份存储在组对象中而不是用户的目录服务,需要功能manages_members。(成员,说白了就是这个组内有多少个用户。我们可以在定义组时,直接说明把哪些用户加到这个组里面来。他们都以这个组为其附加组。))
    The members of the group. For directory services where group
    membership is stored in the group objects, not the users.
    Requires features manages_members.


- **name**
# 这是组名,如果不定义的话,直接使用title来当做其属性值。

- **system**      #(该组是否为GID较低的系统组。 有效值为“ true”,“ false”,“ yes”,“ no”。(是否为系统组。系统组对于centos7来讲就是ID号小于1000的。对于centos6来讲就是ID号小于500的,他的值可以是“ true”,“ false”,“ yes”,“ no”二者都是布尔型值。))
    Whether the group is a system group with lower GID.
    Valid values are `true`, `false`, `yes`, `no`. 

user资源的类型
  管理用户
  常用属性:
        commet:注释信息
        ensure: present, absent
        expiry:过期期限:
        gid:基本组id
        groups:附加组
        home:家目录
        shell:默认shel1
        name: NameVar
        system:是否为系统用户, true|false
        uid: UID
        password: 用户密码
# 列出用户资源的详细使用帮助。
[root@localhost manifests]# puppet describe user
Parameters
----------(它的常用属性有哪些)

- **comment**      #(用户的描述。 通常是用户的全名。(描述信息,说白了就是注释))
    A description of the user.  Generally the user's full name.

- **ensure**      #(对象应处于的基本状态。有效值为“存在”,“不存在”,“角色”。(状态是创建或者是删除。角色是什么?甭管他。))
    The basic state that the object should be in.
    Valid values are `present`, `absent`, `role`. 

- **expiry**      #(该用户的到期日期。 必须以零填充的YYYY-MM-DD格式提供-例如 2010-02-19。 如果要确保用户帐户永不过期,则可以传递特殊值“不存在”。有效值不存在。 值可以匹配`/ ^ \ d {4}-\ d {2}-\ d {2} $ /`。 需要功能manages_expiry。(账号的过期期限))
    The expiry date for this user. Must be provided in
    a zero-padded YYYY-MM-DD format --- e.g. 2010-02-19.
    If you want to make sure the user account does never
    expire, you can pass the special value `absent`.
    Valid values are `absent`. Values can match `/^\d{4}-\d{2}-\d{2}$/`.
    Requires features manages_expiry.

- **gid**
# 他所属的基本组。

- **groups**
# 他所属的附加组。

- **home**
# 家目录。

- **keys**      #(在键=值对数组中指定用户属性。需要功能manages_solaris_rbac。(说白了就是我们可以直接指明他的密钥。每一个用户还要使用生成密钥。这都是密钥对什么之类的。))
    Specify user attributes in an array of key = value pairs.
    Requires features manages_solaris_rbac.

- **name**
# 用户名称

- **password**
# 用户密码

- **password_max_age**      #(在必须更改密码之前,可以使用的最大天数。 需要功能manages_password_age。(密码最长使用期限。))
    The maximum number of days a password may be used before it must be
    changed.
Requires features manages_password_age.

- **password_min_age**      #(密码更改前必须使用的最少天数。要求功能manages_password_age。(密码最短使用期限。这跟我们创建用户的时候,概念都是一样的,只不过我们是要使用属性来描述它而已。))
    The minimum number of days a password must be used before it may be
    changed.
Requires features manages_password_age.

file资源的类型
  主要功能是在于管理文件及其内容、从属关系以及权限:内容可通过content属性直接给出,也可通过source属性根据远程服务器路径下载生成:

  指明文件内容来源:(这三个我之所以写到这儿,是因为他们三个是用来能够指明文件来源。)
        content:直接给出文件内容,支持\n, \t:
        source:从指定位置下载文件:
        ensure: file, directory, link, present, absent

  常用属性:
        force:强制运行,可用值yes, no, true, false
        group:属组
        owner:属主
        mode:权限,支持八进制格式权限,以及u,g,o的赋权方式
        path:目标路径:
        source:源文件路径:可以是本地文件路径(单机模型) ,也可以使用puppet:///modules/module_name/file_name(url路径);
        target:当ensure为"link"时, target表示path指向的文件是一个符号链接文件,其目标为此target属性所指向的路径;此时content及source属性自动失效;
# 列出file资源的详细使用帮助。
[root@localhost manifests]# puppet describe file
file
====
# 管理文件,包括文件的内容,所有权和权限。“ file”类型可以管理普通文件,目录和符号链接; 该类型应在“确保”属性中指定。 请注意,在Windows系统上无法管理符号链接。 文件内容可以直接通过“ content”属性来管理,或者使用“ source”属性从远程源下载。 后者也可以用于递归服务目录(当“ recurse”属性设置为“ true”或“ local”时)。 在Windows上,请注意文件内容以二进制模式进行管理; 木偶永远不会自动翻译行尾。
# **自动要求:**如果Puppet正在管理拥有以下内容的用户或组:
# 文件,文件资源将自动需要它们。 如果Puppet正在管理任何
# 文件的父目录,文件资源将自动需要它们。


Parameters
----------(它的常用属性有哪些)

- **checksum**      #(确定是否替换文件内容时使用的校验和类型。默认校验和类型为md5。 有效值为md5,md5lite,sha256,sha256lite,mtime,ctime,none。(我们要检查校验文件完整性的。说白了就是要指明使用哪一种算法来检查文件的完整性,常用算法有md5,md5lite,sha256,sha256lite,mtime,ctime。none表示不校验,不检查文件))
    The checksum type to use when determining whether to replace a file s
    contents.
    The default checksum type is md5.
    Valid values are `md5`, `md5lite`, `sha256`, `sha256lite`, `mtime`,
    `ctime`, `none`.

- **content**
# 直接给出文件内容,支持\n, \t。 \n表示换行符,\t表示制表符。:

- **source**
# 从指定位置下载文件:(从指定位置下载文件时,有时候如果你下载那个文件路径给定的是一个目录怎么办?那我们还可以使用其他属性来指明递归构建,下载这个目录下的所有文件叫做request。)

- **recurse**
# 是否以及如何进行递归文件管理。一般它应该是一个布尔型值的,false or True ,  仅当指定了源参数时才能使用,*`false` ---没有递归的默认值。
#      有效值为“ true”,“ false”,“ inf”,“ remote”。

- **ensure**
# ensure属性有file, directory, link, present, absent(file, directory, link前面三个主要是区别文件类型的,)

- **force**      
    Perform the file operation even if it will destroy one or more
    directories.
    You must use `force` in order to:
    * `purge` subdirectories
    * Replace directories with files or links
    * Remove a directory when `ensure => absent`
    Valid values are `true`, `false`, `yes`, `no`. 
# (即使将破坏一个或多个目录也要执行文件操作。
#      您必须使用`force`才能:
#      *`purge`子目录
#      *用文件或链接替换目录
#      *当“确保=>不在”时删除目录
#     有效值为“ true”,“ false”,“ yes”,“ no”。(这个意思是,比如说你创建一个文件,它与一个目录同名了,怎么办?这个时候默认情况下,他应该是不会覆盖的。那如果说我们要强制创建的话,他会把那个目录给干掉,没错,只保留了文件,这叫做force,表示强制创建之意。force不是必须要给的属性,所以可以不用指。))

- **group**
# 属组

- **owner**
# 属主

- **mode**
# 权限。(它的权限指定格式长篇大论,可以指明r (read)、w (write)、x (execute/search)、t (sticky)、s (setuid/setgid)、X (execute/search if directory or if any one user can execute)、u (user's current permissions(可以获取用户的当前权限))、g (group's current permissions)、o (other's current permissions)。我们指定权限也是可以使用八进制数字指定方法,像0664,0664,0755等等,也可以使用a=r表示所有人都有读权限,ug+w表示属组和属主还有写权限,也可以使用ug=rw,o=r你可以混合使用多种赋权方式。这比我们chmod功能更强大。)

- **mtime**
# 指明最近一次的修改时间,可以直接指定这个时间值的。

- **path** (*namevar*)
# 非常关键的属性。这是他的目标路径,你要创建文件的话,这个文件放哪儿呀?这就叫做目标路径。建议使用绝对路径。

- **source**
# 这是一个源文件,指明我们复制到这个位置。这个位置就是你path指定的路径。这就是source源和path的意义



# 源怎么指?示例:
        file { "/etc/nfs.conf":
          source => [      # (中括号里面有多个,表示这里指明下载多个,这是一个列表使用中括号括起来的)
            "puppet:///modules/nfs/conf.$host",      # (指明从远程服务器去下载某个文件。)
            "puppet:///modules/nfs/conf.$operatingsystem",
            "puppet:///modules/nfs/conf"
          ]
        }

- **target**
# 链接文件,那我们要指明链接文件,链接是谁啊,是不是很关键,所以当我们指名链接文件时,那一般来讲我们指明path指明target就可以了。一旦我们使用target那我们的source和content就没有任何意义了, 应该明白任何一个链接文件,它是符号链接,符号链接文件没有内容。我们指定target以后,源文件自己的content没用了,没有必要通过指明哪个source去下载文件

尝试创建一个能够创建文件资源的清单
# 切换路径,并查看
[root@localhost ~]# cd manifests/
[root@localhost manifests]# ls
test1.pp

# 创建第二个测试的清单文件(什么属性都没有,给他创建一个空目录。)
[root@localhost manifests]# vi test2.pp

file {'/tmp/mydir':      # 这个叫做file,这里可以只给title(名字),如果只给名字,那就必须使用path给出绝对路径来,否则这里给路径,path可以省略。
        ensure => directory,      # 指明ensurce其类型为directory,
}

file {'/tmp/puppet.file':      # 在来一个file创建一个文件试试
        content => 'puppet testing/nsecond line',      # 直接给出文件内容
        ensure => file,      # 确保他应该是存在的,他应该是一个文件
        owner => 'centos',       # 每一个文件还可以有权限,属组,属主,此前刚好创建了一个centos用户所属distro组,这里直接用上
        group => 'distro',      # 对应的属组distro,这个组要存在
        mode => '0400',      # 指定权限,比如指定为0400,默认的是644,或者664
}

file {'/tmp/fstab.puppet':      # 在来一个文件
        source => '/etc/fstab',      # 其内容是通过别的地方复制过来的,从/etc/fstab复制而来
        ensure => file,      # 确保他应该是存在的,他依然应该是一个文件
}


file {'/tmp/puppet.link':      # 在创建一个文件为链接试试,把上面创建的puppet.file给他创建一个符号链接,这就有依赖关系。好在这里我们先不管他的依赖关系。
        ensure => link,      # 确保其类型为link,因为这时候source没有任何意义了
        target => '/tmp/puppet.file',      # 指明其链接的目标,为哪个路径
}

# 应用一下(-v显示详细信息,还可以加-d显示更详细的信息,都是debug信息有用的信息很少,可以自己去分析。)
[root@localhost manifests]# puppet apply -v -d test2.pp 
Info: Applying configuration version '1602486743'      (信息:正在应用配置版本“ 1602486743”(他自己生成了一个版本号,也可能是一个随机的号码。))

# 验证一下,查看tmp目录下有没有这个几个文件(这就是文件创建方式)
[root@localhost manifests]# ls /tmp -l
-rw-r--r--. 1 root   root   541 10月 12 15:12 fstab.puppet      (这是我们自己定义的。)
-r--------. 1 centos distro  27 10月 12 15:12 puppet.file      (这也是我们自己定义的,其权限为400,属主为centos,属组为distro。)
lrwxrwxrwx. 1 root   root    16 10月 12 15:12 puppet.link -> /tmp/puppet.file      (我们定义的符号链接文件,指向到/tmp/puppet.file)

exec资源的类型
  这是用来运行命令的。

  运行一外部命令:命令应该具有“幂等性"
        幂等性:
              1、命令本身具有需等性:
              2、资源有onlyif, unless,creates等属性以实现命令的条件式运行:
              3、资源有refreshonly属性,以实现只有订阅的资源发生变化时才执行:

  command:运行的命令: MameVar:
  creates:此属性指定的文件不存在时才执行此命令:
  cwd:在此属性指定的路径下运行命令:
  user:以指定的用户身份运行命令:
  group:指定组;
  onlyif:给定一个测试命令:仅在此命令执行成功(返回状态码为0)时才运行command指定的命令:
  unless:给定一个测试命令:仅在此命令执行失败(返回状态码不为0)时才运行command指定的命令:
  refresh:接受到其它资源发来的refresh遇知时,默认是重新执行exec定义的command, refresh属性可改变这种行为,即可指定仅在refresh时运行的命令:
  refreshonly:仅在收到refresh通知,才运行此资源;
  returns:期望的状态返回值,返回非此值时表示命令执行失败:
  tries:尝试执行的次数:
  timeout:超时时长:
  path:指明命令搜索路径,其功能类型PATH环境变量:其值通常为列表[“pathi“, “path2“, 。。.]:如果不定义此属性,则必须给定命令的绝对路径
# 列出exec资源的详细使用帮助。
[root@localhost manifests]# puppet describe exec | less

exec
====
Executes external commands.(执行外部命令。所以exec就是运行我们给定的命令的。)
Any command in an `exec` resource **must** be able to run multiple times without causing harm      (exec资源中的任何命令**必须能够多次运行而不会造成伤害,(这里给定的命令必须能运行多次,而且不会带来损害的。))
that is, it must be *idempotent*.    (也就是说,它必须是*幂等*。 如果你给定的命令,他不具有幂等性的话,那么多次运行可能会导致意外结果,因此这里我们不能随便给命令,最好能确保他们能拥有幂等特性。)
There are three main ways for an exec to be idempotent:      (什么命令拥有幂等行呢,exec成为幂等的主要方法有以下三种:)
* The command itself is already idempotent. (For example, `apt-get update`.)      (首先命令本身已经幂等。 (例如,“apt-get update”我们的升级,无非就是升级一次,如果这一次确实有升级包就升级了,没有可升级的包就不升级了而已。))

* The exec has an `onlyif`, `unless`, or `creates` attribute, which prevents Puppet from running the command unless some condition is met.      ( exec具有`onlyif`,`unless`或`creates`属性,除非满足某些条件,否则它会阻止Puppet运行命令。(这个命令需要拥有onlyif和unless,creates属性,onlyif表示仅在什么时候执行,unless表示仅在什么条件下执行,creates表示条件时运行,我们给明条件,说白了就是我创建一个目录,给一个命令,这个命令是mkdir,创建一个目录,第1次运行没有问题,第2次运行呢,目录已经存在了,或者是我们touch一个文件直接给他覆盖到某个里面去,第1次我们创建了一个新文件,第2次创建但是文件存在了,原来的内容就被冲掉了,所以就会带来一些损害后果。那我们就给他加条件,只有这个文件或目录不存在时,我们才创建。))

* The exec has `refreshonly => true`, which only allows Puppet to run the  command when some other resource is changed. (See the notes on refreshing  below.)      (exec具有`refreshonly => true`,它仅允许Puppet在更改某些其他资源时运行命令。 (请参阅下面的刷新说明。)(refreshonly 表示刷新等于true表示只有在刷新时才运行,只有这个资源依赖的其他资源改变了,我们这个命令才运行,所以这也是条件式运行。))

Parameters
----------(它的常用属性有哪些)

- **command** (*namevar*)
# 运行内存运行命令最关键的就是命令本身,我们可以在title直接定义一个命令,必须要是幂等的想办法变成为幂等性。

- **creates**
# creates指的是此属性指定的文件不存在时,才运行,这表示创建,creates后面跟的是一个文件路径。
# 示例:
        exec { "tar -xf /Volumes/nfs02/important.tar":      (exec 指明tar 命令,展开这个文件)
          cwd     => "/var/tmp",      (表示我们先cd的这个目录里面来再运行命令。)
          creates => "/var/tmp/myfile",      (表示如果/var/tmp/myfile这个文件路径不在,exec指明的tar命令才会运行的,如果存在的话,这个exce压根就不会运行。)
          path    => ["/usr/bin", "/usr/sbin"]
        }

- **cwd**
# 表示在哪个路径下运行命令,说白了就是先cd到这个目录下来再运行命令。

- **environment**
# 环境变量,有些面临的运行需要依赖于环境变量。比如说我们使用一个命令,但是这个命令并不在默认PATH环境变量下,可以在这里直接定义一个PATH就ok,不过这个方法很少用。

- **group**
# 你哪个组的身份来运行命令。  

- **user**
# 以指定用户身份运行命令,一般来讲,我们只给定user就ok,必要时也可以给定group。

- **onlyif**
If this parameter is set, then this `exec` will only run if the command returns 0.  For example:(如果设置了此参数,则只有在命令返回0时,此exec才会运行。意思是只有onlyif指定的命令运行成功了返回值为真了,那么才会运行exec这个指明的命令。因此onlyif应该给定一个测试命令)

- **unless**
If this parameter is set, then this `exec` will run unless the command returns 0.  For example:      (如果设置了此参数,则除非命令返回0,否则此exec将运行。作用于onlyif一样,只不过是一个取反条件。)

- **path**
The search path used for command execution. Commands must be fully qualified if no path is specified.  Paths  can be specified as an array or as a ':' separated list.      (用于执行命令的搜索路径。 如果未指定路径,则命令必须完全合格。 路径可以指定为数组或“:”分隔列表。)

- **refresh**
How to refresh this command.  By default, the exec is just called again when it receives an event from another resource,  but this parameter allows you to define a different command for refreshing.      (如何刷新此命令。 默认情况下,当exec从另一个资源接收到事件时,它会再次被调用,但是该参数允许您定义另一个刷新命令。(说白了就是当这个资源接收到其他资源的refresh通知时怎么办,比如说举个例子,这是一个服务类的资源,配置文件发生改变了,告诉你需要更新,对服务来讲,刷新就是重载或者重启。那对一个命令来讲什么叫刷新呢,比如指定一个创建目录的命令,指定刷新一次,怎么刷新呢?就是把目录删除,再重新创建一次。这叫刷新,但是到底他怎么去刷新?这取决于你的实际需要。所以refresh就是定义在接收到刷新通知时该怎么操作。你可以让他重新运行一次命令,也可以让他运行别的机制,看你的需要,所以这个refresh后面定义的应该又是一个命令之类的。默认情况下,如果你不定义refresh,那么命令就来一次,在运行一次。如果我们定义的话,那就删了,再来一次等等,看你的实际需要。))

- **refreshonly**
# 表示我这个命令压根就不执行,只有别人通知refresh时才运行这个命令吗,这是一个布尔型值,表示True or false。 
# 示例
        exec { newaliases:
          path        => ["/usr/bin", "/usr/sbin"],      (而运行命令时path这个运环境变量是这样指的,path指明其路径。)
          subscribe   => File["/etc/aliases"],      (表示关注订阅了File["/etc/aliases"]这个资源,一旦这个资源发生了变化,这个资源就会给我们发通知。能因此exec 这个命令才会运行newaliases,path这个命令,这是一个命令)
          refreshonly => true      (refreshonly 为true表示exec指明的命令压根不会运行)
        }

- **returns**
# 表示期望得到的返回值,意思是只有你返回这个值,我才认为这个命令执行是成功的,否则就失败的,默认值为0。一般而言无需定义,了解就行。
 
- **timeout**
# 表示超时时长。一个命令一直执行不成功,到了一定时间以后,就超时了

- **tries**
# 执行不成功可以做尝试,做重试,默认值为1就做一次,可以自己指定次数。
 
举个例子
  比如发现某一个服务的配置文件发生改变发生改变时,我们就让这个服务重启,可以使用exec来实现。因为我们直接指明这个命令就行了。
# 测试命令(modprobe 命令用于自动处理可载入模块,运行过一次modprobe后,再来运行一次modprobe也没问题,所以我们说他天然拥有幂等性。那因此像这种天然拥有幂等性的命令,就不用担心他执行多次会出现问题了。用这个命令来做测试。)
[root@localhost manifests]# modprobe ext4
[root@localhost manifests]# lsmod | grep ext
ext4                  571503  0 
mbcache                14958  1 ext4
jbd2                  103046  1 ext4
[root@localhost manifests]# modprobe ext4
[root@localhost manifests]# which modprobe
/usr/sbin/modprobe



# 创建第三个测试的清单文件(命令默认情况下,只能使用绝对路径。)
[root@localhost manifests]# ls
test1.pp  test2.pp
[root@localhost manifests]# vi test3.pp

exec {'/usr/sbin/modprobe ext4':      # 假如运行modprobe命令。
        user => root,      # 以root用户运行命令,当然这里也可以不用指,因为我们当前用户就是root。
        group => root,      # 指定运行命令的组。
        refresh => '/usr/sbin/modprobe -r ext4 && /usr/sbin/modprobe ext4',      # 当我们收到别人refresh通知的时,先给他卸载,再执行一遍,因为默认他是从新来一遍。但我们现在没有订阅这个信息,所以这个现在没有什么用。这里只是写进来。
        timeout => 5,      # 命令的超时时间为5秒。
        tries => 2,      # 重试次数为2次。
}


# 应用资源
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602494149'
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.09 seconds

[root@localhost manifests]# puppet apply -v test3.pp       (我们再来一遍,因为他本身就有这种能力,所以也是没有什么问题,他也不会带来什么损害,但是创建目录就不一定。)
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602494202'
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.07 seconds

# 在编辑这个文件
[root@localhost manifests]# vi test3.pp

exec {'/bin/echo hello > /tmp/hello.txt':      # echo一个命令,直接写入到这个文件中去,这个命令如果执行两遍的话,这种命令它就不具有幂等性了,他会损害此前已有的文件的。
        user => root,      # 就以root用户身份运行
        group => root,      # group指明为root
}

# 应用资源
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602494538'
Notice: /Stage[main]/Main/Exec[/bin/echo hello > /tmp/hello.txt]/returns: executed successfully      (第1个命令向文件中添加内容,成功了。再运行一次。)
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.08 seconds

[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602494606'
Notice: /Stage[main]/Main/Exec[/bin/echo hello > /tmp/hello.txt]/returns: executed successfully      (正常能运行成功是没有问题的,但是他会对这个文件造成损害。puppet自己并不确保执行命令不能有损害性。)
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.08 seconds

# 查看一下换个文件里的内容
[root@localhost manifests]# cat /tmp/hello.txt 
hello

# 改一下资源文件,
[root@localhost manifests]# vi test3.pp
exec {'/usr/sbin/modprobe ext4':
        user => root,
        group => root,
        refresh => '/usr/sbin/modprobe -r ext4 && /usr/sbin/modprobe ext4',
        timeout => 5,
        tries => 2,
}

exec {'/bin/echo keji > /tmp/hello.txt':
        user => root,
        group => root,
        creates => '/tmp/hello.txt',      # 换了,但是这里添加了一个选项,只有在文件不存在的时候才创建。
}

# 应用资源
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.06 seconds
Info: Applying configuration version '1602494986'
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully      (这个exec 运行modprobe 没有问题,那是另外一个命令,没有运行,因为这个文件之前是存在。)
Notice: Finished catalog run in 0.07 seconds

# 把/tmp/hello.txt文件删除了,在看一下效果
[root@localhost manifests]# rm -rf /tmp/hello.txt 
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602495392'
Notice: /Stage[main]/Main/Exec[/bin/echo keji > /tmp/hello.txt]/returns: executed successfully      (这个时候exec这个echo命令才会运行。这时候再执行一遍他又不会运行了,因为文件已存在。可以对文件存在性判断来讲直接使用creates 就行了。)
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.12 seconds
[root@localhost manifests]# cat /tmp/hello.txt 
keji

# 编辑资源文件,使用unless测试文件存在性
[root@localhost manifests]# vi test3.pp
exec {'/usr/sbin/modprobe ext4':
        user => root,
        group => root,
        refresh => '/usr/sbin/modprobe -r ext4 && /usr/sbin/modprobe ext4',
        timeout => 5,
        tries => 2,
}

exec {'/bin/echo keji > /tmp/hello.txt':
        user => root,
        group => root,
        creates => '/tmp/hello.txt',
}

exec {'/bin/echo keji > /tmp/hello2.txt':
        user => root,
        group => root,
        unless => '/usr/bin/test -e /tmp/hello2.txt',      # (test -e测试文件的存在性,除非这个命令运行失败了,'/bin/echo keji > /tmp/hello.txt'这命令才运行,否则不运行,这个命令执行成功了,就不会运行。 )
}

# 在应用资源文件
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602496172'
Notice: /Stage[main]/Main/Exec[/bin/echo keji > /tmp/hello2.txt]/returns: executed successfully      (由于文件是不存在的,unless 测试的确失败了。所以他进行了创建。)
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully
Notice: Finished catalog run in 0.11 seconds

# 在应用一次资源文件
[root@localhost manifests]# puppet apply -v test3.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.05 seconds
Info: Applying configuration version '1602496231'
Notice: /Stage[main]/Main/Exec[/usr/sbin/modprobe ext4]/returns: executed successfully      (这个只运行了modprobe ,不在运行了创建文件。所以说有些命令天生本身不具有幂等性的时候,我们自己可以给他扩展,让他具有幂等性。)
Notice: Finished catalog run in 0.08 seconds

notify资源的类型
  notify类似于shell中的echo主要是用来显示一些信息出来。
# 列出notify资源的详细使用帮助。
[root@localhost manifests]# puppet describe notify

notify
======
Sends an arbitrary message to the agent run-time log.(向代理运行时日志发送任意消息。(你给的什么就是什么,直接就是字符串,没什么更多的需要处理的地方。))


Parameters
----------(它的常用属性有哪些)

- **message**
    The message to be sent to the log. #(消息本身,所以对于我们notify核心属性只有这个message。)

- **name**
    An arbitrary tag for your own reference; the name of the message. #(对应的message名称,名称没有什么太大意义。)

- **withpath**
    Whether to show the full object path. Defaults to false.      (要不要显示完整的目标路径,一般都不用定义他。)
    Valid values are `true`, `false`. 

举个例子
  核心属性:
        message:要发送的消息的内容:

# 创建第四个测试的清单文件(就定义这一个就可以了)
[root@localhost manifests]# vi test4.pp
notify {'hello there':}

# 应用资源文件
[root@localhost manifests]# puppet apply -v test4.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.01 seconds
Info: Applying configuration version '1602500773'
Notice: hello there      (显示的就是这样就这么多。所以他只是一个通知信息。我们把title。当做message了,message是NameVar。)
Notice: /Stage[main]/Main/Notify[hello there]/message: defined 'message' as 'hello there'
Notice: Finished catalog run in 0.04 seconds

cron资源的类型
  定义任务计划资源。

  管理cron任务:
  常用属性:
        ensure: present, absent:
        command:要运行的job;
        hour: 小时:
        minute: 分钟:
        month: 月:
        monthday: 每月第几天:
        weekday: 每周第几天:
        name: 名称:
        user:运行的用户:
        environment: 运行时的环境变量:
cron任务该怎么定义
# 创建第五个测试的清单文件(没写ensure默认为present:表示必需存在,可以添加ensure => absent:表示必需不能有,之前创建了一次,给删除掉)
[root@localhost manifests]# vi test5.pp
cron {'sync time':      # name就是NameVar给个名字就行了,假如叫做sync time我们经常会这样定义,同步时间的
        command => '/usr/sbin/ntpdate 192.168.115.51 &> /dev/null',      # 定义要运行的命令是什么,ntpdate时间同步,还要把无论成功还是失败的结果都送给/dev/null
        minute => '*/10',      # 多长时间运行一次,假如说10分钟运行一次
}


# 应用资源文件
[root@localhost ~]# puppet apply -v test5.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.06 seconds
Info: Applying configuration version '1602663376'
Notice: /Stage[main]/Main/Cron[sync time]/ensure: created
Notice: Finished catalog run in 0.07 seconds

## 查看一下(crontab 用来定期执行程序的命令,-l : 列出目前的时程表)
[root@localhost ~]# crontab -l 
# Puppet Name: sync time      (namevar的title,sync time)
*/10 * * * * /usr/sbin/ntpdate 192.168.115.51 &> /dev/null


package资源的类型
  从名字上至少应该就能感觉出来,这其实是管理程序包的。这是一个非常常用的资源。

  管理程序包:
  常用属性:
        ensure: installed, latest, VERSION(2.3.1-2.el7), present, absent
        name:程序包名称:
        source:包来源:可以本地文件路径或URL;
        provider:指明提供者,他用什么方式,比如rpm。使用source时可以使用rpm进行安装,但是如果使用rpm命令安装的话,还有可能无法解决依赖关系的,所以一旦遇到这种情况怎么办?那我们可以执行自己定义的exec,直接自己使用yum install去安装程序文件,反正是运行命令吗,所以我们通过一个基本的工具完成不了。你会发现exec是万能的,只要是运行命令能完成的,他几乎都能帮你完成。
# 列出package资源的详细使用帮助。
[root@localhost ~]# puppet describe package
package
=======
它主要实现的功能是Manage packages(管理软件包),其实我们能实现程序包安装的各种工具有很多,比如对于红帽而言,我们能够使用RPM。对于Debian而言可以使用deb包,而对于别的发行版而言,它又是其他的包管理工具。好在puppet把目标和实现方式相分离了。因此我们只需要指明要安装的程序包是什么就可以。

Parameters
----------(它的常用属性有哪些)

- **configfiles**
# 配置文件。我们知道我们在安装一个新版本的程序包时,如果它本来已经安装了老的版本的程序包。那么他原有的那个程序包的配置文件怎么处理?
 Whether configfiles should be kept or replaced.  Most packages types do not support this parameter. Defaults to `keep`.Valid values are `keep`, `replace`. (是否应保留或替换配置文件。 大多数软件包类型不支持此参数。 默认值为keep,有效值为keep,replace。)

- **description**
# 描述信息而已,只读的不用管他。

- **ensure**
# 他的目标状态。对于程序包而言它的目标状态有哪些?第一,`latest`安装成为最新版本。当然也可以给名一个version number(安装指定版本),如果一个程序包在yum源中有多个版本,你可以直接指明安装哪个版本。所以ensure后面直接给定一个版本号就行,比如2.3-1什么之类的。`installed`.这是默认安装。`present`也表示安装。`absent`,表示必须不能安装,也就是卸载。

- **install_options**
# 安装选项。
    package { 'mysql':
          ensure          => installed,
          source          => 'N:/packages/mysql-5.5.16-winx64.msi',
          install_options => [ '/S', { 'INSTALLDIR' => 'C:\mysql-5.5' } ],(INSTALLDIR说白了就是安装目录的。由于对windows而言安装mysql需要指明在c盘下的mysql5.5。但事实上对我们的centos而言,大多数的RPM包都不支持重新定位的。所以说大多数情况下,这个选项都无需指明。)
        }

- **name**
# 这个是很关键的。那么说白了,就是装每一个程序包的。

- **source**
# 你要安装一个程序包,这个程序包的源是什么?程序包的安装源,我们可以指定yum以及apt。他们就能自动去下载并安装的。不过如果你指明的安装方式是rpm或者是dpkg的话。就意味着我们本地要自己直接提供rpm包才可以。所以在这里我们要指明安装包的,包文件路径的要指明在什么地方。

- **status**
# 这是他的状态,一般而言是有程序包自己提供的,我们无需指明。

- **allow_virtual**
# 指定是否允许虚拟软件包名称用于安装和卸载。
# 有效值为“ true”,“ false”,“ yes”,“ no”。
# 需要功能virtual_packages。

写一个资源文件试一试
# 创建第六个测试的清单文件
[root@localhost ~]# vi test6.pp
package {'zsh':      # 指名包名叫做zsh。注意这些包可能有依赖关系的,如果有依赖关系,你还要自行保证把这些依赖关系给装上。
        ensure => latest,      # 指定为我们所期望的某一个版本,比如latest。
        allow_virtual => false,

}

package {'jdk':      # 我们还可以安装别的包,比如jdk,这里取名叫jdk,这是他的包名
        ensure => installed,      # 但是如果我们自己指了source的话,就以为这他不会通过集中的源来去安装了,这里ensure为installed。
        source => '/usr/local/src/jdk-8u221-linux-x64.rpm',     # 指明source为/usr/local/src/jdk-8u221-linux-x64.rpm
        provider => 'rpm',
        allow_virtual => false      #这里后面逗号可有可无。

}


# 应用资源文件
[root@localhost ~]# puppet apply test6.pp 

# 验证(可以看到创建了一个Java目录,然后jdk程序包已安装。zsh没有升级)
[root@localhost]~# ls /usr/java 
ls /usr/java
default  jdk1.8.0_221-amd64  latest


service资源的类型
  顾名思义,他是管理服务的。用来实现服务管理。

  管理服务:
  常用属性:
        enable:是否开机自动启动, truelfalse:
        ensure:启动(running),停止(stopped):
        hasrestart:是否支持restart参数:
        hasstatus:是否支持status参数:
        name:服务名称, NameVar
        path:脚本查找路径:
        pattern:用于搜索此服务相关的进程的模式:当脚本不支持restart/status时,用于确定服务是否处于运行状态:
        restart:用于执行"重启"的命令:
        start:
        stop:
        status:
# 列出service资源的详细使用帮助。
[root@localhost]~# puppet describe service

service
=======
Manage running services.(管理正在运行的服务。)Service support unfortunately varies widely by platform(不幸的是,服务支持因平台而异,各种平台上的服务实现方式,都不一样。所以他这里又是一大堆的provider:指明提供者。更重要的是对各种各样的服务都有可能要执行**Refresh:** 操作,因为我们服务所依赖的配置文件发生的改变,那我们本地就要让服务做重启或重载配置文件的。。所以对于服务而言就要用到使用`notify`或者`subscribe`或者使用这种符号`~>`,来实现做订阅的。)

Parameters
----------(它的常用属性有哪些)

- **binary**
# 我们要运行一个服务,应该是通过一个二进制程序文件来启动的。那用的是哪个文件呢?如果要直接指名的话,不是使用脚本,那可以通过binary直接指明程序文件路径的位置。而且在这个binary后面,如果说你是个服务脚本,后面加上start什么之类的都可以的。

- **control**
# 用来管理服务的一般而言是什么unix特用的,所以不用管他。

- **enable**
# 设置为开机是否启动,因此enable为`true`, `false`, `manual`我们手动管理,对于windows而言,他的服务确实有三种方式,自动启动,手动启动或禁止,所以这里有个manual。但对我们Linux而言不太常见。

- **ensure**
# 无非就是几种方式。`stopped`表示停止也叫`false`,`running`表示运行也叫`true`。

- **flags**
# 我们可以自己传递flags,但是对于我们linux而言,这种用法几乎不能见到。所以就甭管他了。

- **hasrestart**
# 在Linux之上,我们要启动一个服务,通过脚本启动或者通过systemctl启动,他里面都可以使用start,restart,stop,status,reload等等,他们分别用来实现不同的控制功能的。因此hasrestart用来说明我当前指明的程序,有自带的restart。就像我们启动nginx的一样,有两种启动方式,第一我们可以自己直接给明二进制程序文件路径,但是这样给了路径他自己就不支持stop什么的参数了,第2种方式我们可以让他通过centos6上的service脚本,或者centos7上的 Unixfile文件,而这种方式在启动时是支持restart。因此我们两种不同的启动方式,有的是带restart参数,有的不带。而这个hasrestart就是指明我们有没有restart,因此它的值就是`true`, `false`。如果有的话就说明他能接受restart这样的指令,所以可以从而完成重启操作的。

- **hasstatus**
# 这个不用解释,应该知道什么意思了,跟上面的hasrestart一样,这个是表示有没有status参数的。

- **manifest**
# 叫清单,这个我们用的很少。

- **name**
# 服务的名称。说白了就是你要运行的一个服务的。

- **path**
# 你的服务的路径。到哪里去搜启动服务的脚本的。

- **pattern**
# 启动服务时可以指定模式。

- **restart**
# 如果一个命令它是没有restart。就是hasrestart为false,那这个时候我们服务又不得不接受别人发过来的refresh通知过来,我怎么重启呀,那因此就要自己定义restart命令了。所以这里的restart就这样定义你自己定义它的restart是怎么实现,比如kill后在启动。

- **start**
# 跟上面一样,他是定义怎么启动一个服务的。

- **status**
# 如果你的脚本或者你的工具自己没有status命令,我们自己指明status命令的。

- **stop**
# 吃stop也是一样的道理。如果你的命令没有stop怎么办?要么kill,或者我们使用别的功能来实现。这指明kill all什么的就能实现了
写一个资源文件,定义一个服务
  那我们来定义一个服务,我们要确保一个服务属于运行状态。首先你得确保那个服务相关的程序包要安装上的,对吧。所以到这个层次上你会发现依赖关系就越来越需要,越迫切。不然的话,我们定义一个服务启动压根程序包没装,何谈启动这么一个概念。
# 创建第七个测试的清单文件
[root@localhost manifests]# vi test7.pp
package {'nginx':      # 首先要做一个首先定义安装一个程序包。这个程序包假如叫nginx。我们在epel源当中,至少是拥有nginx的,先确保nginx能启动起来。
        ensure => latest,      # 定义目标状态为latest,表示安装
}

service {'nginx':      # 接着定义服务,包只要能装上是不是也可以启动服务。而服务名也就叫做nginx
        ensure => running,      # 确保服务是运行的。ensure为running,确保服务是处于running状态。
        enable => true,      # 我们要不要让他开机自动启动。要开机自动启动的话,定义为true即可。
        hasrestart => true,      # 支持不支持restart,支持就为true
        hasstatus => true,      # 支持不支持status,支持就为true
        restart => 'systemctl reload nginx.service',      # 如果我们想让他完成重启或者完成重载怎么办,我们自己定义restart,因为在大多数情况下,别人发的refresh通知的时候是不是都要重启,但是我们这里不能随便重启,应该执行reload命令。
}


# 应用资源文件
[root@localhost manifests]# puppet apply -v test7.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.54 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Info: Applying configuration version '1602726644'
Notice: /Stage[main]/Main/Package[nginx]/ensure: created
Notice: /Stage[main]/Main/Service[nginx]/enable: enable changed 'false' to 'true'
Notice: Finished catalog run in 17.56 seconds

# 验证(查看nginx程序包是否安装,并查看服务是否启动)
[root@localhost manifests]# rpm -q nginx
nginx-1.16.1-2.el7.x86_64

[root@localhost]~/manifests# ps aux | grep 'nginx'
ps aux | grep 'nginx'
root     12692  0.0  0.3  47280  3472 ?        Ss   10月12   0:00 nginx: master process /usr/sbin/nginx
nginx    14297  0.0  0.2  47688  2348 ?        S    09:51   0:00 nginx: worker process

[root@localhost]~/manifests# ss -tunlp
tcp   LISTEN     0      128                                                        *:80                                                                     *:*                   users:(("nginx",pid=14297,fd=6),("nginx",pid=12692,fd=6))


Metaparameters特殊属性
资源的特殊属性,我们在定义资源时,资源当中有这样三类特殊属性。

        第一类称作叫 Name/Namevar(叫名称变量)(Namevar是什么呢,就像我们定义的user一样,前面user这里定义的是keji,这是title,但是我们并没有定义用户名,我们只定义了一个资源他的类型是user,资源他的名称叫keji,但是我们没有定义这个用户名,这个资源的title就成了用户名。这种就叫做Namevar。)
              Most types have an attribute which identifies aresource on the target system(大多数类型的资源都有一个特殊属性,该属性标识目标系统上的资源。)

              This is referred to as the "namevar." and is oftensimply called "name"(所以这个资源通常被称作叫“ namevar”。 而且它对应的有一个属性,这个属性通常也通常就叫做name,。)

              Namevar values must be unique per resource type, withonly rare exceptions (such as exec)(每个资源类型的Namevar值必须唯一,只有极少数的例外(例如exec)所以一般来讲,我们在定义一个资源,是如果没有给name这个title就成了他的name。不过这里还需要强调的是,并不是对所有资源类型来讲,它能够作为Namevar那个属性都叫name,有的不一定。比如说对file来讲,他的Namevar可能叫file,而不叫name。对用户来讲,或对组来讲叫name是容易理解的。所以Namevar他的值必须唯一,对于每一个资源类型来讲值必须唯一。说白了就是这个title必须唯一,因此Namevar也必须唯一,二者在一定程度上指的是同一个东西。)

        第二类称作叫 Ensure(Ensure对于大多数资源来讲都都是拥有的一个属性,一个通用属性,主要是用于控制资源的存在性的。)
              ensure => file:表示存在且为一个普通文件;
              ensure => directory:表示存在且为一个目录;
              ensure => present:表示必需存在,可通用于描述上述三种;
              ensure => absent:表示必需不能有,说白就是有的话就必须给他删除了。不存在;


        第三类称作叫 Metaparameters(我们称作叫元参数,或者叫元属性都可以。元参数和元属性通常指的是哪些所有的类型几乎都支持的,用来定义资源和资源之间关系的,比如像依赖关系啊,通知啊等等,这种属性名称作叫元属性。)
              puppet就是使用有4种所谓的元参数,用来确保资源间的次序这种关系。
                                第一个叫:before
                                第二个叫:require

                                第三个叫:notify
                                第四个叫:subscribe
                                      他们两两一组,第一个和第二个一组,第三个和第四个一组。

                                      这四个元参数都以另外的其它资源或资源数组作为其值,这也称作资源引用(也就以为这before属性的值,只能是其他资源,事先定义过的其他资源。所以这里是做资源引用的。)
                                            资源引用要通过"Type ['title']"的方式进行,如User['maged'],
                                                  注意:资源引用时,其类型名的首字母要大写(Type首字母必须大写)

               Metaparameters主要有三类:
                    第一类是定义资源引用
                          资源引用要通过"Type ['title']"的方式进行,如User['maged'],
                                                  注意:资源引用时,其类型名的首字母要大写(Type首字母必须大写)
                    第二类是定义依赖关系
                          从名字上应该就能看得出来,依赖关系指的是,一种资源是依赖于另外一种资源的,所以资源之间其实有次序的概念叫次序。
                          被依赖的资源中使用: before
                          依赖其它资源的资源: require
                                他们二者效果是一样的,没必要同时使用。如果要构建一个链式依赖关系,我们还可以使用箭头符号。


                          示例:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                      before => User['suse'],      # 所以一定要记得,我们引用一个别的资源时,或者说你定义的资源有可能产生依赖关系的时候,我们要确保那个被依赖的资源得事先存在。比如说group定义的组id,他是user的基本组 。所以group 这个资源,要先与user这个资源,要先于实现。就是用before,意思是我要在某个资源前面,比如在User['suse']这个资源前面,这就表示引用另外一个资源了。从而我们就定义了依赖关系。                                                         
                                }

                                user {'suse':      # 如果说我们第2个定义的user ,他所属的组为,其组为2000的组,但是如果group定义组不存在或者,group定义的组id为3000,user定义的组id为2000怎么办。创建会失败的。
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                }

                          或者:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                                    
                                }

                                user {'suse':
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                      require => Group['linux'],      # 我们也可以反过来定义在user中定义group要事先存在。定义require意思是说这个资源运行要必须保证Group这个资源类型中,linux要事先存在。这两种功能是一样的。
                                }
                                      应用一下这个资源:puppet apply -v test8.pp他一定会确保,group先运行的,这个没有任何问题,因为我们定义了require 


                          或者:
                                group {'linux':
                                      gid => 3000,
                                      ensure => present,
                                                    
                                } ->      # 我们还有另外一种表现方式。直接说明他俩有依赖关系。这个箭头表示第1个先第2个后,就表示第1个先于第2个。这就是一种链式关系。

                                user {'suse':
                                      uid => 3000,
                                      gid => 3000,
                                      shell => '/bin/bash',
                                      home => '/home/suse',
                                      ensure => present,
                                }

                    第三类是定义通知关系
                          被依赖的资源中使用: notify
                          监听其它资源的资源: subscribe


                          示例:(这里面定义的有一个package,一个service,我们知道这个service必须要依赖于package)
                          [root@localhost manifests]# mkdir -pv /root/modules/nginx/files
                          [root@localhost manifests]# cp /etc/nginx/nginx.conf /root/modules/nginx/files 
                          [root@localhost manifests]# vi /root/modules/nginx/files/nginx.conf      (修改一下,主要是以示区别。)
                          worker_processes  2;
                              server {
                                  listen       8080;

                          [root@localhost manifests]# vi test9.pp 
                                package {'nginx':
                                      ensure => latest,
                                            # 一般来讲,我们在安装一个程序包以后,能够依赖于他的默认配置文件直接运行吗?很少,那我们还可能有一个配置文件样本。
                                }

                                file {'/etc/nginx/nginx.conf':
                                      ensure => file,      # 确保他是文件
                                      source => '/root/modules/nginx/files/nginx.conf',       # suorce源,文件从哪来.可以想象一下,我们去复制这个文件到/etc/nginx/目录时.
                                      require => Package['nginx'],       # 如果你的程序包没装的话,一般而言/etc/nginx这个目录是不存在的,所以我们可以理解为,这个资源要依赖于package,不装包的话,我们复制文件没有用.所以定义require依赖于package这个资源,资源名字为nginx
                                }

                                service {'nginx':
                                      ensure => running,
                                      enable => true,
                                      hasrestart => true,
                                      hasstatus => true,
                                      restart => 'systemctl reload nginx.service',
                                      require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],      # 同样的我们也可以认为service要想启动的话,我们没有file,没有package,其实没有任何意义的,或者是没有file也没关系, 至少package 是安装上的,如果我们要定义service依赖于package 和file 两个资源,我们就要使用列表的方式给出,两个被依赖到的资源。这种就叫做属组。
                                            # 一般而言,如果file资源,配置文件发生了改变,我们应该让服务资源重启。
                                }
                                                  应用一下这个资源:puppet apply -v test9.pp,hash值发生了变化,但是我的那个服务并没有重启。因此这个require定义不了重启关系的。这就是另外两个关系的属性了 notify,subscribe这两个刚好相对应。notify第一在前资源,subscribe定义在后资源。定义在前资源就表示自己发生了改变,通知给其他资源。后资源就表示自己监控的别的资源,如果别人资源发生了改变,我就自己做什么操作,所以这叫做订阅叫subscribe,他们两个用一个就行。一般来讲如果一个资源需要通知一个资源的话就可以定义在前资源中。如果一个资源都被很多种资源所依赖的话,那就定义在后资源中。

                          或:
                                [root@localhost manifests]# systemctl stop nginx      #(停掉服务)
                                [root@localhost manifests]# vi /etc/nginx/nginx.conf      (修改回去。)
                                                        worker_processes  2;
                                                            server {
                                                                listen       80;
                                [root@localhost manifests]# vi /root/modules/nginx/files/nginx.conf      (修改一下,主要是以示区别。)
                                                        worker_processes  3;
                                                            server {
                                                                listen       8080;

                                [root@localhost manifests]# vi test9.pp 
                                      package {'nginx':
                                            ensure => latest,
                                      }

                                      file {'/etc/nginx/nginx.conf':
                                            ensure => file,
                                            source => '/root/modules/nginx/files/nginx.conf',
                                            require => Package['nginx'],
                                            notify => Service['nginx'],      # 在前面这个file发送改变了,notify发送通知给Service这个类型的资源中的叫做nginx资源
                                      }

                                      service {'nginx':
                                            ensure => running,
                                            enable => true,
                                            hasrestart => true,
                                            hasstatus => true,
                                            # restart => 'systemctl reload nginx.service',      (这个一个就没用了,reload就没办法让新监听的端口生效。就先把它注释掉。)
                                            require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
                                      }
                                                  应用一下这个资源:puppet apply -v test9.pp。这次只有文件发生了改变。Scheduling refresh of Service[nginx]通知调度Service[nginx]做refresh。使用ss -tunlp可以看到监听在8080

简单写一个应用

# 定义一个清单,创建一个目录,专门保存这个清单。
[root@localhost ~]# mkdir manifests
[root@localhost ~]# cd manifests/
[root@localhost manifests]# ls

# 创建一个清单,第一个清单就叫做test1.pp(其实叫什么都无所谓了,一般约定俗成都是一点pp结尾。)
[root@localhost manifests]# vi test1.pp
group {'distro':      # 定义一个组资源,这个组的资源名字叫distro
        gid => 2000,      # 对于一个组来讲,他最常用的属性就是GID了,假如说等于2000
        ensure => present,      # 而且这个组是必须要存在,还是不能存在呢。我们要创建就使用ensure为persent
}      # 这是第一的第一个资源


user {'centos':      # 在这个组内再创建一个用户,再来一个资源user。这个user我们叫distro可不可以?可以同一个类型下不能同名,但不同类型是可以同名的。这里无所谓了,这里假如叫centos
        uid => 2000,      # 这其实都是字符串,即便你输入的是2000,看这是一个数值,它也是个字符串而已,职位上一般而言就可以使用引号引用起来。
        gid => 2000,      # gid假如要加入到2000这个组里面来,就设置为2000。
        shell => '/bin/bash',      # shell类型比如是'/bin/bash'我们是用引号引起来了。
        home => '/home/centos',      # home家目录一般在/home/叫做centos。
        ensure => present,      # 我们这个用户是创建还是删除呀?ensure为什么,如果要创建的话就为present,这里不加引号,这不是字符串,他是一个状态,这种状态类似于布尔型的值,所以不能随便加引号的,像gid,uid这种是可以加引号的,但是如果中间没有空格的话不加引号应该也没有问题。
}      # 这个时候我们这样的一个清单,我们已经定义过了,清单定义完以后,它的主要作用是期望让应用清单的站点能处于目标状态的。任何一个清单应该先去编译成catalog我们才能去应用,而且应用时还要先查他的状态,就当前的状态,然后再去在本地完成应用。

编译资源清单

  我们要完成应用的话使用apply这个令。在本地直接不经过主存模式,直接手动来应用资源清单。所以这个命令叫做puppet apply,使用puppet help apply可以获取apply使用帮助。
# 查看帮助
[root@localhost manifests]# puppet help apply
puppet-apply(8) -- Apply Puppet manifests locally(在本地应用资源清单)

USAGE
-----
puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose]
  [-e|--execute] [--detailed-exitcodes] [-l|--logdest <file>] [--noop]
  [--catalog <catalog>] [--write-catalog-summary] <file>      (用法puppet apply指明<file>你的资源清单文件是什么就ok了。如果这个资源清单文件是编译过的catalog,我们甚至也能使用--catalog 指明<catalog>文件是什么就可以了。      或者是我们在指名 <file>时,使用--catalog表示把这个文件编译完以后,把catalog放在何处并且叫什么名字,这都是ok的。      -V大小是显示apply版本号。      -d小写则能显示apply过程的Debug信息。      -v小写能显示详细过程信息。大体上就这几个常用选项。)

# 编译资源清单(-v显示详细信息,后面跟上资源清单文件。)
[root@localhost manifests]# puppet apply -v test1.pp
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.20 seconds(注意:环境生产中localhost.mshome.net的编译目录在0.20秒内(当前这个节点是localhost.mshome.net的主机名。现在是在生产环境做了编译,什么叫生产环境呢?其实puppet可以支持同时应用于多种不同环境,比如测试环境、生产环境和开发环境,我们企业内部通常也有这三种环境,对不对。这里默认都是生产环境。))
Info: Applying configuration version '1602415571'(信息:正在应用配置版本“ 1602415571”(他自己生成了一个版本号,也可能是一个随机的号码。))
Notice: /Stage[main]/Main/Group[distro]/ensure: created(注意:/ Stage [main] / Main / Group [distro] /确保:已创建(第一个group就是组,组类型的中间有一个组名叫distro创建完成了))
Notice: /Stage[main]/Main/User[centos]/ensure: created(注意:/ Stage [main] / Main / User [centos] /确保:已创建)
Info: Creating state file /var/lib/puppet/state/state.yaml(信息:创建状态文件/var/lib/puppet/state/state.yaml(创建完成以后,创建了一个状态文件,他也是使用yaml来保存其状态结果的。))
Notice: Finished catalog run in 0.13 seconds(注意:完成的目录将在0.13秒内运行(在0.13秒内这个catalog就运行完成了))

# 查看状态的报告结果(我们无需关心这里面的结果,了解就行。)
[root@localhost manifests]# cat /var/lib/puppet/state/state.yaml

# 验证用户和组是否创建完成(就这么简单,这就是所谓的叫资源清单,在一个文件中定义多个资源,所以他被称作叫清单,也就是这个道理。)
[root@localhost manifests]# tail /etc/group
postfix:x:89:
chrony:x:996:
sshd:x:74:
tss:x:59:
stapusr:x:156:
stapsys:x:157:
stapdev:x:158:
nginx:x:995:
puppet:x:52:
distro:x:2000:      (我们所创建的distro组)

[root@localhost manifests]# tail /etc/passwd
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:997:User for polkitd:/:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
nginx:x:997:995::/home/nginx:/bin/bash
puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin
centos:x:2000:2000::/home/centos:/bin/bash      (我们所创建的centos用户)

puppet中的变量及其作用域

  变量名均以s开头,斌值符号;任何非正则表达式类型的数据均可赋值给变量:
        puppet当中的变量不但类似于其他的编程语言,事实上puppet中的变量也跟我们此前所讲到的shell编程中一样,每一个变量还都有它的作用范围。比如在shell讲到的环境变量,本地变量还有局部变量,他们的生效范围是各不相同的。对于puppet中的变量亦是如此。      首先对于puppet的而言变量与其他编程语言没有什么不同,因为本来puppet就使用的ruby语言所研发,所以他的很多使用风格完全遵循ruby语言的编程方式。而所有编程语言几乎都一样,变量只能包含字母,数字,下划线,而且不能以数字开头。而对于puppet而言,所有变量名都以$开头。这表示我们后面定义的字符串,它是一个变量。而赋值符号是=等号。他也有等值比较,另外在puppet所支持的所有数据类型中,只要不是正则表达式的那种,都可以直接赋值变量。

  作用域 : 定义代码的生效范围,以实现代码间隔离
        仅能隔离:变量,资源的默认属性:
        不能隔离:资源的名称,及引用: 

               puppet变量它又有其作用域的概念。什么是作用域说白了就是定义代码的生效范围的,以实现代码间隔离。这个我们在其他编程语言在shell中也提到过。而对于变量而言,它的隔离,一般来讲,能隔离的只是变量或者资源的默认属性,而资源名称及引用时没办法隔离的,你可以在代码中的任何地方去引用此前定义作的资源。所以可以在任何地方引用,这个是没有办法隔离,但是能隔离的对puppet而言仅能隔离变量和资源默认属性。不能隔离的,就是压根就隔离不了,在任何处都可以引用的,指的是资源的名称。      所以说只要在同一个模块中定义的任何资源,无论你定义在什么位置,那么同一类型下的资源都一定不能同名。      但变量名不是这样子的,在不同位置你可以使用同一个变量。而对于puppet的而言,它的作用域大体上分为这么几层。

                    Top Scope顶级作用域他的生效方式,全局有效。第2类Node Scope作用域称作叫节点作用域,它的生效范围是对整个节点有效, puppet是master/agent类型的,所以所谓全局终于表示对所有节点都有效。Node Scope表示仅对一个节点有效,同样的在一个节点上,我们将来可以定义很多类叫做class。一个类就相当于我们在shell脚本中的一个函数这样的说法并不精确,这里只是给你说的作用域范围。所以一个类可以定义一个作用域。就像我们在函数中用local来定义一个变量,那这个变量仅在当前函数中生效。但是如果假设一个函数中还能嵌套其他函数,那么在父函数中定义的,在子函数可以用,一样有效,但是在子函数中定义的在父函数中就无效。      。也说白了就是小范围可以引用它的外部的大范围的。但外部大范围不能引用它内部的小范围。

        puppet的每个变量都有两个名字:简短名称和长格式完全限定名称(FQN),完全限定名称的格式为"$scope:variable"

        puppet语言支持多种数据类型可以实现向变量赋值,也可以用来向属性赋值,每一个属性都类似一个变量。 

        每个变量两种引用路径:
              相对路径
              绝对路径: $::scope::scope::variable

        变量的赋值符号:
              =:      # 赋值
              +=:      # 追加默值

        数据类型:
              布尔型: ture, false
              undef:未声明
              字符型:可以不用引号,支持单引号(强引用),双引号(弱引用)
              数值型:整数和浮点数:
              数组:[iteml, item2, ...],元素可为任意可用数据类型,包括数组和hash;紊引从开始,还可以使用负数:
              hash: (key => value, key => value, ...),键为字符串,而值可以是任意数据类型:

              正则表达式:
                    非标准数据类型,不能赋值给变量:
                          语法结构:
                                (?<ENABLED OPTION> : <SUBPATTERN2)
                                (-<DISABLED OPTION> : <SUBPATTERN>)

                                OPTION:
                                      i:忽略字符大小写:
                                      m:把.当换行符:
                                      x:忽略模式中的空白和注释:

              表达式:
                    比较操作符:==, !=, <, <=, >, >=, =~, !~, in(等于,不等于,小于,小于等于,大于,大于等于,能被模式匹配,不能被模式匹配,in)
                    逻辑操作符: and, or,!(与,或,非。)
                    算术操作符: +, -, *, /, %, >>(右移位), <<(左移位)
                                                                    示例:(左移位)
                                                                         1011
                                                                        1011      所谓左一位就是向左移一位,少一位进行补0,就为10110。所以补0就相当于乘以他的节数,比如说1变成10,就相当于乘个10,一后面加俩0就相当于乘以100。本来是3,现在后面加三个0,就相当于乘以1000。所以要看是几进制的,如果是二进制要乘以2。

                                                                    示例:(右移位)
                                                                          1011
                                                                           101      向右移一位最后一位就没了。左侧补一个0。为0101




  字符型
        非结构化的文本字符串,可以使用引号,也可以不用;
        单引号中的变量不会替换,而双引号中的能够进行变量替换;
        字符型值也支持使用转义符;

  数值型
        有两种,可为整数或浮点数,不过, puppet只有在数值上下文才把数值当数值型对待(说白了就是默认数型值当做字符串,只有参与运算时才临时转换数值,puppet其实也是弱类型的,对类型的要求并不是那么严格。),其它情况下一律以字符型处理;

  数组
        数组值为中括号"[]"中的以逗号分隔的项目列表,最后一个项目后面可以有逗号;
        数组中的元素可以为任意可用数据类型,包括hash或其它数组;
        数组索引为从0开始的整数,也可以使用负数索引;

  布尔型
        true和false,不能加引号(因为不是字符串);
        if语句的测试条件和比较表达式都会返回布尔型值;
        另外,其它数据类型也可自动转换为布尔型,如空字符串为false等;

  undef(表示未定义的类型,变量值为空,他没有报错,但是值为空。)
        从未被声明的变量的值类型即为undef:
        也可手动为某变量赋予undef值,即直接使用不加引号的undef字符串;

  hash
        即为键值数据类型,键和值之间使用"=>"分隔,键值对儿定义在"{}"中,彼此间以逗号分隔:
        其键为字符型数据,而值可以为puppet支持的任意数据类型;
        访问hash类型的数据元素要使用"键"当作索引进行
              示例:(这就是哈希类型.集合)
                    {'1'=>'red', '2' => 'blue'}

  正则表达式
        属于puppet的非标准数据类型,不能赋值给变量,仅能用于有限的几个接受正则表达式的地方,即接受使用"=~"及"=~"匹配操作符的位置,通常包括case语句中的selector,以及节点名称匹配的位置;
        它们不能传递给函数或用于资源属性的定义;
        puppet中的正则表达式支持使用(?<ENABLED OPTION>: <SUBPATTERN),<ENABLED OPTION>表示启用的选项,<SUBPATTERN表示模式。和(?-i<DISABLED OPTION:<SUBPATTERN,)两个特殊的符号,      -i<DISABLED OPTION中的-i表示不忽略字符大小写      -减号表示取反
              例如下面的示例表示做正则表达式匹配时启用选项"i"(忽略字符大小写),但不支持使用"m"(把当作换行符)和"x"(忽略模式中的空白字符和注释)
                    示例:这表示,如果说变量$operatingsystem,这个变量的值。如果能够被ubuntu或debian匹配到,说白了就是这个变量值是ubuntu或debian。那于是我们就把$packages这个变量值赋值为apache2。
                          $packages = $operatingsystem ? {
                                /(?i-mx: ubuntu | debian)/      => 'apache2',
                                /(?i-mx: centos | fedora | redhat)/ => 'httpd',      # 这是一个分支,上面的没有匹配上就匹配下面的。
                          }

举个例子

[root@localhost manifests]# cp test9.pp test10.pp
[root@localhost manifests]# vi test10.pp 
$webserver = nginx      # 取一个变量就$webserver,给他赋一个值等于nginx。所以我们将来想安装tengine时,把这里换成tengine即可。
package {$webserver:      # 我们这里引用时直接写上变量名$webserver,不要使用单引号。
        ensure => latest,
}

file {'/etc/nginx/nginx.conf':
      ensure => file,
      source => '/root/modules/nginx/files/nginx.conf',
      require => Package['nginx'],
        notify => Service['nginx'],
}

service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
}

# 应用一下(我们这里并没有报什么错,因为我们什么都没有改,只是把变量所引用的名字改了一下。。)
[root@localhost manifests]# puppet apply -v test10.pp 

# 我们再给他改一下试试。
[root@localhost manifests]# systemctl stop nginx.service      (把服务给他停掉)
[root@localhost manifests]# yum history list nginx      (卸载nginx)
[root@localhost manifests]# yum history undo 10      (卸载nginx)
[root@localhost manifests]# vi test10.pp
$webserver=nginx
package {$webserver:
        ensure => latest,
}

file {'/etc/nginx/nginx.conf':
      ensure => file,
      source => '/etc/nginx/nginx.conf',      (自定义的配置文件路径还要指定并且要保证有配置文件存在,比较麻烦,直接使用安装默认的配置文件。)
      require => Package['nginx'],
      notify => Service['nginx'],
}

service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
}



# 再次应用一下
[root@localhost ~]# puppet apply test10.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.64 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Notice: /Stage[main]/Main/Service[nginx]/ensure: ensure changed 'stopped' to 'running'
Notice: Finished catalog run in 1.16 seconds



puppet中变量的种类

  puppet中变量可以有其他种类。
        自定义变量
              可以自己按需要来定义变量。
        facter变量
              查看puppet支持的各facts:
                    facter -p
  内置变量:
        客户端内置:
              $clientcert      # 客户端证书
              $clientversion      # 客户端的puppet版本号
        服务器端内置
              $servername       # 服务器名称
              $serverip      # 服务器地址
              $serverversion      # 服务器版本号,就是服务端程序文件版本号。
              $module_name      # 模块名称

        每一个系统都要报告自己的各种信息。包括我们操作系统硬件设备,各种各样的值都要报告给提交给那个服务端,有服务端把对应的模板中的那些引用的变量的值都给他替换成相应的值就可以了。像这些变量都是可以直接引用的,因为每一个note在应用它的资源清单manifests,都会自己先提交自己的facter的。而我们facter命令。我们在装puppet时专门装了一个facter的包。rpm -q facter或rpm -qi facter进行查看
# 查看包文件
[root@localhost ~]# rpm -q facter
facter-2.4.1-1.el7.x86_64

# 查看帮助(facter本身是一个命令,-p表示输出为puppet可是别的所有的facter有哪些)
[root@localhost ~]#  facter --help
facter [-h|--help] [-t|--timing] [-d|--debug] [-p|--puppet] [-v|--version]
      [-y|--yaml] [-j|--json] [--plaintext] [--external-dir DIR] [--no-external-dir]
      [fact] [fact] [...]

# 显示当前主机向puppet请求自己相关的facter时,所有要提交的facter变量。(有没有发现跟Ansible很接近?其实Ansible用的也是facter,这是专门的一个facter包。)
[root@localhost ~]#  facter -p
operatingsystem => CentOS
memoryfree => 304.50 MB      (空闲内存空间)
manufacturer => Microsoft Corporation      (你的硬件制造商这里使用的 Windows自带的虚拟机,虚拟出来的。)
physicalprocessorcount => 1      (物理CPU数量)
processorcount => 1      (逻辑CPU数量,就是核心数)
selinux => true      ( selinux有没有启用)
selinux_config_mode => enforcing      (selinux当前启用的模型是什么,是enforcing)
fqdn => localhost.mshome.net      (当前节点名称)
ipaddress => 192.168.115.51      (当前IP地址,如果有多个地址的话,可能写成一个中括号括起来的列表)

puppet所支持的条件判断语句

  puppet2.7支持'if', 'case', 'selector'

  puppet3支持unless语句,就是跟if相反,相当于if not
        'if'支持单分支if CONDITION...      ,双分支if CONDITION...else... ,多分支if CONDITION...lesif CONDITION...else...

              单分支                         双分支
            if CONDITION {                  if CONDITION {
                  ...                           ...    
              }                              }
                                                ...
                                             }
                                             else {          
                                                ...
                                             }

        对于if语句中的 conditions条件可以这样写,能够用在if语句中的conditions有
                                                                    Variables可以是一个变量,
                                                                    Expressions,可以是一个表达式,一般而言是一个比较表达式。
                                                                    Functions that return values可以是一个拥有返回值的函数。
简单的示例
[root@localhost ~]# vi test11.pp

if $processorcount>1 {      # 内容是if条件判断。任何变量都要加$符,如果说这个变量值大于1,我们这里写的是一个比较表达式。如果是一个变量就只有$processorcount,变量如何判断是真还是假,一个未声明的变量就为假声,声明了的变量就为真。如果对于一个整形的变量,零值表示为假非零表示为真。对于字符串型变量,空串表示为假非空表示为真。
        notice('SMP Host.')      # 调用它的内置函数notice,这个函数说白了,就是你给他什么他就显示什么,类似于print打印。
} 
else {      # lese否者
        notice('Poor Guy.')      # 调用它的内置函数notice,这个函数说白了,就是你给他什么他就显示什么,类似于print打印。
}

# 应用一下这个资源
[root@localhost ~]# puppet apply test11.pp 
Notice: Scope(Class[main]): Poor Guy.      (显示Poor Guy说明我们cpu核心数没有大于1。)
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.01 seconds
Notice: Finished catalog run in 0.02 seconds




puppet如果做正则表达式应用

  if $operatingsystem =~ /^(?i-mx: (centos | redhat))/ {      # if $operatingsystem表示判断操作系统类型是什么,$operatingsystem表示取得这变量值,=~表示模式匹配,整个变量值到底是什么。/^(?i-mx: (centos | redhat))/表示正则表达式模式,她的固定格式,如果后面要写一个正则表达式模式的话,我们应该把模式放在/双斜线内部/。^托字符表示以这里面模式所指明的字符开头。
        notice("Welcome to $1 linux server")       # $1如果匹配到了redhat就引用redhat,$1这叫做后向引用。

}

简单的示例
[root@localhost manifests]# vi test12.pp
if $operatingsystem =~ /^(?i-mx:(centos|redhat|fedora|ubuntu))/ {
        notice("Welcome to $1 distribution linux")
}

# 应用一下这个资源
[root@localhost manifests]# vi test12.pp
[root@localhost manifests]# puppet apply test12.pp       
Notice: Scope(Class[main]): Welcome to CentOS distribution linux      # 这里显示CentOS
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.01 seconds
Notice: Finished catalog run in 0.03 seconds

case语句

  与shell编程的case语句并无两样,子不过格式上有所不同而已,它类似于if语句,case语句的主要作用是取代多分支的if语句,用一个更简洁的方式进行书写

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

        示例:
              case CONTROL_EXPRESS {      # 使用case加上CONTROL_EXPRESS(叫控制表达式),用花括号括起来然后指明分支1,casel,case2,case3,case4:冒号,整个语句可能是多个这里使用...表示,这个控制表达式指的是,如果CONTROL_EXPRESS这个值刚好能够被第一种情况所匹配casel,...其中任何一个值所匹配,那么就执行{ statement... }分支一,如果第一种情况中众多case都不能匹配,就判断第二个分类中的case2。如果所有都不能匹配就default
                    casel,case2,...: { statement... }
                    case3,case4,case5,...: { statement...}
                    ... ...
                    default: { statement... }
              }




        它所支持的控制表达式有,就是CONTROL_EXPRESS这个表示的控制表达式有:(CONTROL_EXPRESSION:表达式、变量、函数(有返回值))
                                                  Variables # 可以是一个变量
                                                  Expressions # 可以是一个表达式
                                                  Functions that return values # 有返回值的函数(跟我们if语句是一样的)

        匹配到了以后可以是casel,case2多种情况,这个casel可以是什么?(case:字符串,变量,有返回值函数,模式, default)
                                                  A literal value (remember to quote strings)      # 可以是一个直接的值
                                                  A Variables      # 可以是一个变量
                                                  A function call thatreturns a value      # 返回值的函数调用,只能是一个返回值的函数
                                                  A regular expression      # 这个casel还可以是一个正则表达式
                                                  The special bare wordvalue default      # 说白了就是可以使用default这个字串,用来做定义默认分支

        示例:
               case $operatingsystem {      # 如果这个$operatingsystem能够被一下任何条件匹配,就执行对应的分支的notice
                    'Solaris': {notice ("Welcome to Solaris") }
                    'RedHat','CentOS' : { notice("Welcome to RedHat OSFamily")}
                    /^(Debian|Ubuntu)$/: { notice("Welcome to 11 linux")}
                    default:             { notice ("wel come, alien **")}

              }

selector语句

  类似于case,但分支的作用不在于执行代码片断,而是返回一个直接值(说白了就是一个字符串):

  selector类似于case,但是不同的地方在于selector
        Selector statements are similar to case statements,but return a value instead of executing a code block(选择器语句类似于case语句,但是返回值而不是执行代码块)
        selector只能用于期望出现直接值(plain value)的地方,这包括变量赋值、资源属性、函数参数、资源标题、其它selector的值及表达式
        selector不能用于一个已经嵌套于于selector的case中,也不能用于一个已经嵌套于case的case语句中

  selectors使用要点:
        整个selector语句会被当作一个单独的值, puppet会将控制变量按列出的次序与每个case进行比较,并在遇到一个匹配的case后,将其值作为整个语句的值进行返回,并忽略后面的其它case

        控制变量与各case比较的方式与case语句相同,但如果没有任何一个case与控制变量匹配时, puppet在编译时将会返回一个错误,因此,实践中,其必须提供default case

        selector的控制变量只能是变量或有返回值的函数,切记不能使用表达式

        其各case可以是直接值(需要加引号)、变量、能调用返回值的函数、正则表达式模式或default

        但与case语句所不同的是, selector的各case不能使用列表

        selector的各case的值可以是一个除了hash以外的直接值、变量、能调用返回值的函数或其它的selector

                  总结:
                    CONTROL-VARIABLE:可以是,变量、有返回值的函数;但不能是表达式:

                    case:可以是,直接值(需要带引号)、变量、有返回值的函数、正则表达式模式或default

  示例:
        $webserver = $operatingsystem ? {     # 先不看这个$webserver =变量值本身,先判断$operatingsystem这个值符合哪个分支,如果符合第一个分支,我们就直接把apache2这个值丢出去了,直接返回,所以不管丢出哪一个它多赋值给$webserver这个变量,      所以这个整个就是一个变量赋值语句,而这个变量赋值语句的值是一个selector
              /(?i-mx:ubuntu|debian)/      => 'apache2',
              /(?i-mx:centos|fedora|redhat)/ => 'httpd',
              default => 'no'      # 对于selector而言必需有一个default 
        }
        if $webserver {
                notice('zhe shi:',$webserver)
        }

puppet中的类:class

  用于公共目的的一组资源,是命名的代码块;创建后可在puppet全局进行调用,类可以被继承。类是我们用来构建模儿的基本组件。
        类的主要作用主要是用来设定,一组公共资源,而后给这组公共资源的代码块起一个名字,将来任何时候我们用到是只需要使用名字来调用此代码块就可以了。所以说对于class来讲,创建后可在puppet全局进行调用。

  类的名称只能以小写字母开头,可以包含小写字母、数字和下划线(说白了就是不能够使用大写字母)

  每个类都会引入一个新的变量scope,这意味着在任何时候访问类中的变量时,都得使用其完全限定名称(也就意味着每一个类都会引入一个作用域。在一个类中声明引用的变量,是不会被父类所调用的。,但是可以被它的子类所调用。因此这就意味着在类中访问变量时,都得使用其完全限定名称。也就意味着你在类以外的地方,引用类中的变量试,要使用它的完全限定名称。)
        不过,在本地scope(作用域)可以重新为top scope(顶级作用域的变量)中的变量赋予一个新值(说白了就是覆盖。)


  语法格式:
        class class_name {
              ...puppet code...
        }

        示例:(在一个程序文件中定义的函数。如果把某段代码放在函数中,它不会直接运行。当我们调用函数时,它才会运行类也是如此,只不过类我们通常不称作叫调用,而称作叫声明,我们声明一个类的时候,类才能被调用。也就意味着也就意味着类中的代码才会被执行的。)
              class apache {      # 定义了一个class叫apache,整个类内部我们定义了三个资源
                    package { httpd:      # 第一个资源叫做package
                          ensure > installed
              }
              file { 'httpd.conf':      # 第二个资源叫file,而且还定义了feel能够require=>Package  。
              path -> '/etc/httpd/conf/httpd.conf',
              ensure => file,
              require => Package ['httpd'],
              }
              service {httpd:      # 第三个资源叫service,并且service有subscribe,需要注意的是subscribe隐含有依赖关系。一个资源订阅了另外一个资源,那另外一个资源要事先存在才行。所以他隐含本身就有依赖关系的。
                    ensure => running,
                    require => Package["httpd"],
                    subscribe-> File[“httpd.conf“],

写一个资源文件,定义一个类class

[root@localhost manifests]# vi test13.pp



class nginx {      # 我们定义一个类,假如就叫做nginx。
    $webserver=nginx      
    package {$webserver:      # 在其内部我们定义多个资源,第1个资源先安装程序包。
        ensure => latest,
   }

   file {'/etc/nginx/nginx.conf':
        ensure => file,
        source => '/etc/nginx/nginx.conf',
        require => Package['nginx'],
        notify => Service['nginx'],
   }

   service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
   }

}


# 停掉nginx服务
[root@localhost manifests]# systemctl stop nginx

# 卸载程序
[root@localhost manifests]# yum history list nginx
[root@localhost manifests]# yum history undo 4

# 删除其配置文件,让其恢复到原始状态
[root@localhost manifests]# rm -rf /etc/nginx/

# 应用一下资源(告诉你什么都没运行,虽然我们那些程序包都没有安装,而且服务也没运行,配置文件也没有。但是这个资源照样能运行。因为类定义后没有被声明,说白了就是没有被调用。)
[root@localhost manifests]# puppet apply -v test13.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.02 seconds
Info: Applying configuration version '1602897304'
Notice: Finished catalog run in 0.04 seconds

声明一个类class

  注意:类在声明后方才执行:

  类声明的方式:
        include class_name, class_name,...

  定义能接受参数的类:
        (类还可以带参数的,我们这里class当中的$webserver定义的是nginx,而不是Tengine,假如说我们事先并无法确定他是什么,而且我们不打算在类中直接进行定义,我们可以使用一个参数来传递。比如很多程序包都有很多版本的选择,所以我们事先要装哪一个呢?所以事先可能会在调用时通过其他代码段直接传递各参数过来。所以类还可以带参数的。)
        class class_name($argl='valuel', $arg2='value2') {      (而后在类的代码段中去引用这些参数就ok了,这里给关键字传参,以防止别人调用时没有给传参,这个形参就没有值了,没有值的话有可能会导致puppet代码运行失败。如果没有就使用默认值。如果你定义了,传参数了,他就会取代这里的默认值。)
              ... puppet code ... 
        }


  在manifest文件中定义的类不会直接被执行,它们需要事先声明后才能被执行,而声明类的方式有4种。最常用的是第一种,和第三种 。

        Declaring a Class With include
        Declaring a Class with require
        Declaring a Class Like a Resource      (像声明一个资源一样来声明一个类)
        Declaring a Class With an ENC      (使用ENC的风格来上面一个类。)

  Declaring a Class With include(使用include声明类,使用include声明类的方法)
        Classes can be declared with the include function(可以使用include函数声明类)
              include base::linux(非常简单使用include加类名就可以了,就这么容易,这种就是定义在基类,和继承类的)

        Can safely use include multiple times on the same class and it will only be declared once(可以安全地在同一类上多次使用include,并且只会声明一次)
              
        The include function can accept a single class name or a comma-separated list of class names(include函数可以接受单个类名或用逗号分隔的类名列表)
              include base::linux, apache(而且一个include可以使用逗号隔开声明多个类)

  Declaring a Class with require(用require声明一个类,第2种我们可以使用require)
        The require function acts like include, but also causes the class to become a dependency of the surrounding container(require函数的行为类似于include,但也会使该类成为周围容器的依赖项,这种方式很少用)
              define apache::vhost (Sport,$docroot, $servername, $vhost_name) {
                    require apache      (一样的使用require加类名就可以。)
                    ...
              }

  Declaring a Class Like a Resource(像资源一样声明类,声明一个资源,使用类型有个Title,各属性等于值。因此我们说明一个类也是一样。)
        Classes can also be declared like resources, using the special "class" resource type(也可以使用特殊的“类”资源类型,像资源一样声明类)

              # Declaring a class with the resource-like syntox
              class {'apache':      (使用class 关键字加上一个类的名称。)
                    version => 2.2.21,      (还可以定义一些属性,不是版本而是属性。有些人可能认为很奇怪,对一个类来讲属性有什么用?其实一个类可以接受参数,就像一个函数可以接受参数一样。所以我们在声明类时的传递的属性就相当于向它的参数赋值的。)
              }
              # With no panameters:
              class {'base::linux':}

        A class can only be declared this way once(一个类只能以这种方式声明一次)

        The resource-like syntax should not be mixed with include“for“d given class(类似于资源的语法不应与包含“ for”给定类混合使用)
# 了解第一种使用include来声明一个类
class nginx {
    $webserver=nginx
    package {$webserver:
        ensure => latest,
   }

   file {'/etc/nginx/nginx.conf':
        ensure => file,
        source => '/etc/nginx/nginx.conf',
        require => Package['nginx'],
        notify => Service['nginx'],
   }

   service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
   }

}

include nginx      (加上一个include,实例化这个类,这就是python中的类调用。)

# 应用一下资源(我们这里配置文件发生了改变但是没有通知服务,如果加上notify和subscribe就不会有这种现象发生了,这里的服务是直接启动的)
[root@localhost manifests]# puppet apply -v test13.pp 
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.84 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Info: Applying configuration version '1602902038'
Notice: /Stage[main]/Nginx/Package[nginx]/ensure: created
Notice: /Stage[main]/Nginx/Service[nginx]/ensure: ensure changed 'stopped' to 'running'
Info: /Stage[main]/Nginx/Service[nginx]: Unscheduling refresh on Service[nginx]
Notice: Finished catalog run in 71.95 seconds


# 通使用类传参
[root@localhost manifests]# vi test14.pp 

class nginx($webserver='nginx') {      (# 在nginx后面写一个括号,将参数放这里)
    package {$webserver:      (# 这里调用webserver)
        ensure => latest,
   }

   file {'/etc/nginx/nginx.conf':
        ensure => file,
        source => '/etc/nginx/nginx.conf',
        require => Package['nginx'],
        notify => Service['nginx'],
   }

   service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
   }

} 

include nginx      (# 这样声明是不可能传递参数的,如果我们不想安装nginx,这个时候想安装Tengine,我们只需要在声明这里类的时候,把这个类当中形参$刀了号webserver改成Tengine即可,怎么改?这就要用到下一种声明类的方式,像声明一个资源一样,声明一个类)


# 第二种,类声明方式(像声明一个资源一样,声明一个类)(在我们epel源中是没有tengine包的,这里运行的话,可能会失败)
class nginx($webserver='nginx') {      
    package {$webserver:
        ensure => latest,
   }

   file {'/etc/nginx/nginx.conf':
        ensure => file,
        source => '/etc/nginx/nginx.conf',
        require => Package['nginx'],
        notify => Service['nginx'],
   }

   service {'nginx':
        ensure => running,
        enable => true,
        hasrestart => true,
        hasstatus => true,
     #  restart => 'systemctl reload nginx.service',
        require => [Package['nginx'], File['/etc/nginx/nginx.conf'] ],
   }

}

class { 'nginx:      # nginx这是类名
        webserver => 'tengine'      # 第一个属性叫做,webserver他其实就是一个属性,值为tengine
}

类继承

  作用:继承一个已有的类,并实现覆盖资源属性,或向资源属性追加额外值:
        =>      # 一个子类从一个父类继承过来资源以后,父类中所定义的很多资源都有属性,对不对?那如果说父类中的某个资源属性不想要了,想换一个怎么办?那可以覆盖,所以我们可以使用特定符号=>,在子类中对父类中某个资源属性重新赋值,来实现。
        +>      # 如果说我们想在父类资源中给他额外新增一个值,则使用+>, 这种方式来实现。

  类继承时,有几点要注意:
        (1)声名于类时,其基奖会被自动首先声明:
        (2)基类成为了子类的父作用域,基类中的变量和属性默认值会被子类复制一份:
        (3)子类可以覆盖父类中同一资源的相同属性的值:


              什么叫继承呢?举个例子,比如小明自己有一套房子,后来是Daddy,不幸,故去了。他从Daddy那里继承了一套房子。所以请问小明还有几套房子?应该知道怎么继承了吧,其实简单来讲,类继承所实现的方式也是一样的法则。首先他从他的父类那里可以得到所定义的所有内容,而且在父类的基础上还可以添加新的内容。比如说他从父类那里继承了一套房子,他又买了一个新的房子,于是他有了两个房子,一个是继承的,一个是自建的。那于是对于类继承来讲,只不过他的实现方案或者它的主要作用在于继承一个已有类,而且还可以实现覆盖资源的某些属性。

定义方式:

  class base_class {      (# 定义好基类)
        ... puppet code ...
  }

  class baseclass::class_name inherits baseclass {      (# 仍然是class关键字,但是后面定义名字。第一先使用基类名叫base_class他的名字,然后再使用两个冒号在指明你当前类的名字。这就表示这个类叫做一个名字class_name,但是这个类会先从基类上继承一些内容。那一般而言,为了表明类和类之间的关系,我们可以此类命名时。被继承类叫基类,也叫父类,继承类叫子类。子类在命名时通常使用这种格式,把父类名然后加上两个冒号,跟上当前的一个名字进行定义,这仅仅是一个名字,主要是为了避免误解,以为它是自己独立的类,其实不是,他是一个继承。。      关键要指明的是继承哪一个,叫inherits表示继承至谁?比如base_class,当然了,我们首先要定义好基类才行。)
        ... puppet code ...      # 下面继续写puppet代码就可以了
  }

应用类继承

  # 在一个基类中定义安装程序包,定义启动服务。安装程序包,启动服务和安装程序包就是一个公共功能了
[root@localhost manifests]# vi test15.pp
class nginx {      # 先定义类,假如父类就叫nginx
        package {'nginx':      # 对于父类而言,假如说他不接受参数。直接定一个对应的资源就可以了,比如package,起名为nginx
                ensure=>latest,      # 对应的属性为ensure为latest
        }  ->       # 我们在定义第2个资源,第2个资源是依赖于第1个资源,使用这个符号。      

        service {'nginx':      # 第2个资源叫做server,对应的nginx。
                enable => true,      # 这个资源的ensure为true,假如表示开机自动启动。
                ensure => running,      # 确保这个服务为running的。
                hasrestart => true,      # hasrestart是否有restart,如果有为true
                hasstatus => true,      # 是否有shastatus,如果有为true
                restart => 'service nginx reload',      # 如果我们要对他做restart了怎么办?一般而言他只要不改端口,我们都可以做systemctl reload nginx.service。当然了,这样写并不对,这只适用于centos7。可以使用service nginx reload这样都可用了。
                                          # 他要去订阅上面这个资源, Sorry,他不能订阅这个资源。而是要订阅配置文件这个资源。但是配置文件没有定义。因此我们让通配置文件通知他,是不是就OK。
        }
}      # 假如说我们父类中只有这一个资源,我们让资源直接启动起来也是可以的。这就定义好了,这是第1个是他的父类。

class nginx::webserver inherits nginx{      # 接着我们定义子类。第1个子类做web server。叫做nginx下的web server这是用来做web服务器,指明其inherits继承至nginx。
      file {'/etc/nginx/nginx.conf':      # 然后我们可以去定义一个新的资源,叫file。其配置文件为/etc/nginx/nginx.conf
                source => '/root/modules/nginx/files/nginx_web.conf',     # 为了以示区别,把这个nginx他的源文件定义成叫nginx_web.conf。他只不过复制过去之后都任然叫做/etc/nginx/nginx.conf。这里指的是原文件。所以/root/modules/nginx/files/这个路径下可以指定这两个文件,一个用来做web的,一个用来做proxy的。
                ensure => file,       # ensure确保其为一个文件。
                notify => Service['nginx']    # notify要通知,要引用Service这个资源当中被称作为nginx的服务
        }
}     

class nginx::proxy inherits nginx {      # 再来一个基类,nignx::proxy做反代的,inherits指明继承至nginx,下面的内容跟上面几乎是一样的。
        file {'/etc/nginx/nginx.conf':
                source => '/root/modules/nginx/files/nginx_proxy.conf',      # 我们为了以示区别,把这个nginx他的源文件定义成叫nginx_proxy.conf。他只不过复制过去之后都任然叫做/etc/nginx/nginx.conf。我们这里指的是原文件。所以/root/modules/nginx/files/这个路径下可以指定这两个文件,一个用来做web的,一个用来做proxy的。看到了把 这就叫类继承
                ensure => file,
                notify => Service['nginx'],
        }
}      所以同样的服务程序包安装,我们额外分别经过两个不同的配置文件,从而实现了他们能够运用在不同的用途上。能够用到不同的节点上。 但是我们此刻不知道该怎么声明,怎么声明呢,你是声明nginx还是webserver,还是proxy呢 ?什么时候才能用呢。将来我们做master/agent模型时,某个应用,第一个node我们去include class nginx::webserver这么一个类,第二个node去include class nginx::webserver这个一个类,这两个节点就分别扮演了不同的角色

include nginx::webserver      # 这里我们include nginx::webserver声明一个,声明两个没办法同时执行的



# 应用一下(提示/root/modules/nginx/files/nginx_web.conf文件不存在,创建文件,在应用)
[root@localhost manifests]# puppet apply -v test15.pp
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.75 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Info: Applying configuration version '1603106214'
Error: /Stage[main]/Nginx::Webserver/File[/etc/nginx/nginx.conf]: Could not evaluate: Could not retrieve information from environment production source(s) file:/root/modules/nginx/files/nginx_web.conf
Notice: /Stage[main]/Nginx/Service[nginx]: Dependency File[/etc/nginx/nginx.conf] has failures: true
Warning: /Stage[main]/Nginx/Service[nginx]: Skipping because of failed dependencies
Notice: Finished catalog run in 4.87 seconds

## 复制配置文件
[root@localhost manifests]# cd /root/modules/nginx/files/
[root@localhost files]# ls
[root@localhost files]# cp /etc/nginx/nginx.conf nginx_web.conf
[root@localhost files]# cp /etc/nginx/nginx.conf nginx_proxy.conf
[root@localhost files]# ls
nginx_proxy.conf  nginx_web.conf

## 修改配置文件
[root@localhost files]# vi nginx_proxy.conf 
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
                proxy_pass http://192.168.11.50/;      # 把所有请求给他反代至192.168.11.50
        }

# 在应用一下(这次没报什么错)
[root@localhost files]# cd -
/root/manifests
[root@localhost manifests]# puppet apply -v test15.pp
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.94 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Info: Applying configuration version '1603106720'
Notice: Finished catalog run in 1.15 seconds

## 我们在修改一下配置文件(声明proxy这个类,上面哦我们修改的配置文件是这个类的配置文件,应用一下)
[root@localhost manifests]# vi test15.pp 
include nginx::proxy

# 在应用一下(你会发现配置文件改了,服务被Scheduling,那这个时候他就工作在反代模式了,这就是类继承,注意类的特征可以继承,覆盖)
[root@localhost manifests]# puppet apply -v test15.pp
Notice: Compiled catalog for localhost.mshome.net in environment production in 0.75 seconds
Warning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.
   (at /usr/share/ruby/vendor_ruby/puppet/type.rb:816:in `set_default')
Info: Applying configuration version '1603107093'
Info: /Stage[main]/Nginx::Proxy/File[/etc/nginx/nginx.conf]: Filebucketed /etc/nginx/nginx.conf to puppet with sum 0cda04255bb93b4fdca098d14f1807dc
Notice: /Stage[main]/Nginx::Proxy/File[/etc/nginx/nginx.conf]/content: content changed '{md5}0cda04255bb93b4fdca098d14f1807dc' to '{md5}fdfbc237f9bdacbf863140b82e87b4b4'
Info: /Stage[main]/Nginx::Proxy/File[/etc/nginx/nginx.conf]: Scheduling refresh of Service[nginx]
Notice: /Stage[main]/Nginx/Service[nginx]: Triggered 'refresh' from 1 events
Notice: Finished catalog run in 0.98 seconds


# 修改配置文件,验证覆盖(在子类中覆盖父类中已经定的资源的属性值:。如果有tengine这个包,直接应用就可以了,如果没有会报错,没有这个包)
[root@localhost manifests]# vi test15.pp 
class nginx {
        package {'nginx':
                ensure=>latest,
                name => nginx,      # 添加一个属性,会覆盖父类中的name的值
                # name +> tengine      # 这表示在父类的nginx基础之上,在装一个tengine,就是我们装一包,不光有nginx还有tengine这里只是举例子
        }   ->

class nginx::webserver inherits nginx{
        Package['nginx']{      # 在子类中,我们想安装的是tengine,先引用这个类,在进行修改
                name => tengine      # 将name这个值修改为tengine      
        }

posted @ 2020-10-01 12:52  给文明以岁月  阅读(693)  评论(0编辑  收藏  举报