原文地址:Language Guide
Puppet語言通過資源描述的方式管理我們的機器,它讓這一切工作都變得簡單而有效。本指南展示了Puppet語言是如何工作的,以及Puppet語言的一些基礎概念。學習Puppet語言非常重要,它是幫助你理解Puppet如何管理你的機器的關鍵。
Puppet語言相比其它編程語言而言是相當簡單的。閱讀本指南,也可以幫助你了解大量其它人已經寫好的Puppet模塊。Modules頁提供了關於模塊的更多信息和鏈接。
名稱中可接受的字符集
變量名只能夠包含字母數字和下划線,大小寫敏感。連字符是不允許的,有些Puppet版本允許它們,這是一個Bug。
類名,模塊名,the names of defined,和自定義的資源類型被限制為只能使用小寫字母,數字和下划線,且必須以小寫字母開頭,也就是必須匹配表達式[a-z][a-z0-9_]*。有些名稱違反了這些限制,但是目前能夠正常工作,這是不建議的。連字符是嚴重反對使用的,在新版本里面它會讓類里面的變量不能夠正常工作。
類和資源類型定義可以使用命名空間分割符(::)。Class and defined resource type names can use ::
as a namespace separator, which is both semantically useful and a means of directing the behavior of the module autoloader. The final segment of a qualified variable name must obey the restrictions on variable names, and the preceding segments must obey the restrictions on class names.
Parameters used in parameterized classes and defined resource types can include alphanumeric characters and underscores, cannot begin with an underscore, and are case-sensitive. In practice, they should be treated as though they were under the same restrictions as class names in order to maximize future compatibility.
There is no practical restriction on resource names.
Any word that the syntax uses for special meaning is a reserved word, meaning you cannot use it for variable or type names. Words like true
, define
, inherits
, and class
are all reserved. If you ever need to use a reserved word as a value, be sure to quote it.
資源
Puppet的基礎是資源。資源描述了系統的多個方面,例如一個文件,一個服務,一個包,甚至是一個自定義的資源。我們稍后會降到如何使用"defines"和"classes"將資源聚合在一起,如何使用"moudles"將"defines"和“classes”再組合在以前。但現在我們一切從資源開始。
每個資源都有一個類型、標題和其它很多的屬性 - Puppet的資源能夠支持各種各樣的屬性,它們中的很多都有對應的默認值,所以不用擔心要為每一個都指定值。
可以在"References"找到Puppet支持的所有資源類型,它們的可用屬性,以及很多的文檔。
下面是一個簡單的資源使用例子,定義了一個文件的權限和所有者:
file { '/etc/passwd': owner => 'root', group => 'root', mode => '0644', }
這段代碼在所有的機器上都能夠執行,可以用它檢查密碼文件是否配置正確。
冒號前面的內容是資源的標題,必須是唯一的,能夠被其它的資源引用。標題后面是一系列的屬性和它們的值。
絕大多數的資源都有這樣一個屬性(經常被定義為name),當沒有為它指定值時默認與資源的標題相同。在Puppet內部,這被稱之為"namevar"。例如file資源,path屬性默認與標題相同。資源的namevar幾乎都是唯一的,只有exec資源和notify資源是例外。
對簡單資源來說,不用明確地指定name或path屬性,讓Puppet自動設置成同title屬性,這樣就足夠了。但是有些擁有很長名稱的資源,或者在不同的操作系統擁有不同的文件名,這時候通常會使用更容易理解的符號標題:
file { 'sshdconfig': path => $operatingsystem ? { solaris => '/usr/local/etc/ssh/sshd_config', default => '/etc/ssh/sshd_config', }, owner => 'root', group => 'root', mode => '0644', }
使用sshdconfig作為標題,使得該file資源更容易被其它資源應用,因為無論是何種操作系統,它的標題都是相同的。例如,我們增加一個依賴該file資源的服務:
service { 'sshd': subscribe => File['sshdconfig'], }
這會使得sshd服務在sshdconfig被改變時重新啟動。注意,當我們引用一個資源時,需要大寫資源類型的首字母,例如File[sshdconfig]。當看到一個大寫的資源類型,需要明白那實際上就是一個資源引用。小寫字母是用來定義的。資源只能被定義一次,重復定義相同的資源會導致錯誤。這是Puppet非常重要的特性,使得能夠按照模塊化非常好的組織配置信息。
一個資源能夠依賴多個資源嗎?從Puppet 0.24.6開始可以安裝下面的方式指定多個依賴:
service { 'sshd': require => File['sshdconfig', 'sshconfig', 'authorized_keys'] }
元參數
Puppet提供了一些被稱為"元參數(metaparameters)"的全局屬性。元參數能夠象其它屬性一樣使用,所有的資源類型都支持元參數。
在上面的例子中就使用了兩個元參數:subscribe和require,都是用來定義資源之間關系的。可以在“Metaparameter Reference”所有元參數的列表。
資源默認值
有時候需要為某類資源指定一個默認的參數。Puppet提供實現這樣功能的語法,使用沒有標題的大寫資源首字母方式。例如,為所有的執行命令設置默認的path參數:
Exec { path => '/usr/bin:/bin:/usr/sbin:/sbin' } exec { 'echo this works': }
第一行代碼為exec資源提供一個默認的path參數。exec資源需要一個完整的限定路徑或者能夠通過path參數找到具體的可執行文件。資源定義時若有必要可以覆蓋path的默認設置。這種情況下我們可以為所有的配置指定一個默認path參數,特殊情況時覆蓋它就可以了。
Puppet默認值支持所有的資源類型。
默認值不是全局的 - 它只在當前范圍和當前范圍下面有效。如果你想為所有的配置設置默認值,唯一選擇是在任意類的外面定義它們。
Resource Collections
Aggregation is a powerful concept in Puppet. There are two ways to combine multiple resources into one easier to use resource: Classes and defined resource types. Classes model fundamental aspects of nodes, they say “this node IS a webserver” or “this node is one of these”. In programming terminology classes are singletons — they only ever get evaluated once per node.
Defined resource types, on the other hand, can be reused many times on the same node. They essentially work as if you created your own Puppet type just by using the language. They are meant to be evaluated multiple times, with different inputs each time. This means you can pass variable values into the defines.
Both classes and defines are very useful and you should make use of them when building out your puppet infrastructure.
類
類定義以class關鍵字開始,內容放在花括號里面。下面是一個管理兩個文件的簡單類例子:
class unix { file { '/etc/passwd': owner => 'root', group => 'root', mode => '0644'; '/etc/shadow': owner => 'root', group => 'root', mode => '0440'; } }
這里使用了一種簡便寫法。下面的例子是相同的:
class unix { file { '/etc/passwd': owner => 'root', group => 'root', mode => '0644', } file { '/etc/shadow': owner => 'root', group => 'root', mode => '0440', } }
class freebsd inherits unix { File['/etc/passwd'] { group => 'wheel' } File['/etc/shadow'] { group => 'wheel' } }
如果子類需要取消父類的某些設置,可以這樣處理:
class freebsd inherits unix { File['/etc/passwd'] { group => undef } }
執行時,代理節點會包含/etc/shadow文件,群組屬性變為wheel;同時也會包含/etc/passwd文件,群組屬性保持不變。
class freebsd inherits unix { File['/etc/passwd', '/etc/shadow'] { group => 'wheel' } }
在Puppet 0.23.1或更高版本,可以使用‘+>’ (‘plusignment’) 操作符為資源參數添加值。
class apache { service { 'apache': require => Package['httpd'] } } class apache-ssl inherits apache { Service['apache'] { require +> File['apache.pem'] } }
這個例子使得apache服務除了需要httpd包外,還需要apache.pem文件。
為了增加多重需求,還可以使用數組方式:
class apache { service { 'apache': require => Package['httpd'] } } class apache-ssl inherits apache { Service['apache'] { require +> [ File['apache.pem'], File['/etc/httpd/conf/httpd.conf'] ] } }
這個例子中require參數等同於:
[Package['httpd'], File['apache.pem'], File['/etc/httpd/conf/httpd.conf']]
同資源一樣,類與類之間也可以使用'require'參數建立關聯:
class apache { service { 'apache': require => Class['squid'] } }
這個例子使用require參數使得apache類依賴於squid類。
在Puppet 0.24.6或更高版本,可以像下面一樣指定多重關聯:
class apache { service { 'apache': require => Class['squid', 'xml', 'jakarta'], } }
require不會自動地定義類,也就是說它可以使用多次,且兼容參數類,但是必須確保require中的類已經定義過。
參數類
class apache($version) {
... class contents ...
}
node webserver { class { 'apache': version => '1.3.13' } }
class apache($version = '1.3.13', $home = '/var/www') { ... class contents ... }
運行階段
運行階段功能是從Puppet 2.6.0被加入的。運行階段為我們提供了另外一種控制Puppet資源執行順序的方法。如果有大量的資源要執行,而且它們之間的執行順序很重要,這將是一個很頭痛的問題,尤其是明確地管理資源之間的關系。這種情況下,運行階段讓我們可以將一個類關聯到某個運行階段。Puppet確保了所有的運行階段都能夠以一個預期的順序執行。
為了使用運行階段功能,除了預先定義的主階段外,其它的階段都必須先定義才能使用。我們可以使用與定義資源關系相同的語法(例如 “->”和“<-“)為運行階段指定執行順序。運行階段質之間關系確保了與之關聯的類的執行順序。
Puppet預定義了一個名為"main"的運行階段,所有的類默認地都關聯到該階段,除非有明確的指定。在只有一個運行階段情況下,Puppet的執行效果和以前版本是相同的,除非有明確地指定資源之間的關系,否則資源會以一種不可預期的方式被執行。
運行階段的定義方法:
stage { 'first': before => Stage['main'] } stage { 'last': require => Stage['main'] }
所有關聯到first階段的類都會在關聯到main階段的類之前被執行。所有關聯到last階段的類都會在關聯到main階段的類之后被執行。
運行階段被定義后,可以使用"stage"參數關聯一個類都某個階段:
class { 'apt-keys': stage => first; 'sendmail': stage => main; 'apache': stage => last; }
關聯apt-keys類的所有資源到first階段,關聯sendmain類的所有資源到main階段,關聯apache類的所有資源到last階段。這段簡短的定義確保了apt-keys類的所有資源都會在sendmain類的所有資源之前執行,apache類的所有資源都會在sendmain類的所有資源之后執行。
資源類型(Defined Resource Types)
define svn_repo($path) { exec { "/usr/bin/svnadmin create ${path}/${title}": unless => "/bin/test -d ${path}", } } svn_repo { 'puppet_repo': path => '/var/svn_puppet' } svn_repo { 'other_repo': path => '/var/svn_other' }
為資源類型創建資源時,參數($path)以資源屬性的方式(path => '/var/svn_puppet)使用,同時在資源類型的內部也可以作為變量使用。參數可以使用等號(=)設置默認值,這樣當為資源類型創建資源時就可以不需要強制指定。
資源類型可以使用一些內置的變量,例如$name和$title,當資源被創建時這兩個變量被設置成資源的標題屬性。在Puppet 2.6.5或更高版本,$name和$title變量也可以作為參數的默認值:
define svn_repo($path = "/var/${name}") {...}
創建資源時的參數在資源類型內部都可以作為變量使用:
define svn_repo($path) { exec { "create_repo_${name}": command => "/usr/bin/svnadmin create ${path}/${title}", unless => "/bin/test -d ${path}", } if $require { Exec["create_repo_${name}"] { require +> $require, } } } svn_repo { 'puppet': path => '/var/svn', require => Package['subversion'], }
這不是一個很專業的例子,大多數時候我們都知道subversion還需要svn簽出,但它演示了require的用法和其它參數做為變量的使用。
資源類型象類一樣在名稱里面可以使用命名空間分隔符(::)。當引用一個資源類型實例時,必須大寫所有類型名稱的手字母(例如:Apache::Vhost['wordpress'])。
類 vs. 資源類型
類和資源類型定義方式很相似,但使用方法卻很不同。
資源類型在同一主機可以重用,允許存在多個實例。Defined types are used to define reusable objects which will have multiple instances on a given host, so they cannot include any resources that will only have one instance. For instance, multiple uses of the same define cannot create the same file.
另一方面,類確保了它必定是單實例的 - 可以包含它們很多次,但都是指向同一個實例。
通常,服務會被定義成一個類,服務包、配置文件和守護進程都放在一起,因為同一主機大多數情況只有服務的一個實例。
資源類型用來管理諸如虛擬主機類似可以有多個的資源,或者用來包裝一些重復的定義以減少輸入。
模塊
可以(強烈建議)將很多的類,資源類型和資源定義封裝在一起作為一個模塊使用。模塊是一種可以共享(portable)的配置信息集合,例如,一個模塊可以包含配置Postfix或Apache服務所需要的全部資源。參閱"模塊"可以獲取更詳細的信息。
鏈接資源
從Puppet 2.6.0開始,資源之間可以通過關系定義從而將它們鏈接在一起。
按照下面的方式定義"before"關系:
File['/etc/ntp.conf'] -> Service['ntpd']
要求ntp配置文件必須在ntpd服務之前被執行。
按照下面的方式定義"notify"關系:
File['/etc/ntp.conf'] ~> Service['ntpd']
要求ntp配置文件必須在ntpd服務之前被執行,同時ntpd服務改變時也通知ntp配置文件。
可以在同一行指定多重關系,從而形成關系鏈:
Package['ntp'] -> File['/etc/ntp.conf'] -> Service['ntpd']
首先執行ntp包,然后執行ntp配置文件,最后執行ntpd服務。
有時也不需要所有的箭頭都指向同一方向:
File['/etc/ntp.conf'] -> Service['ntpd'] <- Package['ntp']
執行完ntp包和ntp配置文件后再執行ntpd服務。注意,使用這種語法定義關系必須是在相鄰資源之間。例如,ntp包和ntp配置文件之間互相沒有直接關系,Puppet有可能在執行ntp包之前就先執行ntp配置文件,這不是我們所期望的結果。這種語法提高了一些代碼的可讀性。
也可以在資源定義時指定關系,上面的例子可以修改成:
package { 'ntp': } -> file { '/etc/ntp.conf': }
確保在執行ntp配置文件之前先執行ntp包。
But wait! There’s more! You can also specify a collection on either side of the relationship marker:
yumrepo { 'localyumrepo': .... }
package { 'ntp': provider => yum, ... }
Yumrepo <| |> -> Package <| provider == yum |>
This manages all yum repository resources before managing all package resources that explicitly specify the yum provider. (Note that it will not work for package resources that don’t specify a provider but end up using Yum — since this relationship is created during catalog compilation, it can only act on attributes visible to the parser, not properties that must be read from the target system.)
This, finally, provides easy many to many relationships in Puppet, but it also opens the door to massive dependency cycles. This last feature is a very powerful stick, and you can considerably hurt yourself with it. In particular, watch out when using virtual resources, as the collection operator realizes resources as a side-effect.
節點
理解了資源、類、資源類型和模塊,就已經學會了Puppet的大部分內容,剩下的就是節點了。
節點定義也象類定義一樣,支持繼承。Puppet服務器包含一個節點定義列表,當一個節點(執行Puppet客戶端的主機)連接到Puppet主進程,就按照它的名字在列表中查找。找到的節點信息被執行,然后發送給節點主機(Puppet客戶端).
節點名字可以是短的主機名,也可以是完整的限定域名(full qualified domain name,FQDN). 有些名字,特別是FQDN,必須要用引號包含起來,最好的方式是所有名字都引起來。例如:
node 'www.testing.com' { include common include apache, squid }
上面的代碼定義了一個名為"www.testing.com"的節點,包含common, apache和squid類。
也可以用逗號分割多個節點,為它們指定相同的配置:
node 'www.testing.com', 'www2.testing.com', 'www3.testing.com' { include common include apache, squid }
上面的代碼定義了三個相同配置的節點: www.testing.com
,www2.testing.com和
www3.testing.com。
使用正則表達式匹配節點
從Puppet 0.25.0或更高版本,節點可以使用正則表達式進行匹配。例如:
node /^www\d+$/ {
include common
}
上面的代碼匹配所有名稱以www開頭,后面有一個或多個數字的主機。
另外一個例子:
node /^(foo|bar)\.testing\.com$/ {
include common
}
上面的代碼匹配test.com網域中名稱為foo或bar的主機。
如果同一個配置文件中,與當前連接客戶端匹配的有使用主機名的節點定義,還有多個使用正則表達式的節點定義,Puppet會如何處理呢?
- 如果存在使用主機名的節點定義,它會被優先使用。
- 否則,執行第一個匹配的正則表達式節點定義。
節點繼承
節點支持受限的繼承。和類一樣,節點只允許單繼承:
node 'www2.testing.com' inherits 'www.testing.com' { include loadbalancer }
www2.testing.com節點繼承了www.testing.com節點的所有配置,另外還包含loadbalancer類。
默認節點
如果創建一個名為default的節點,它將作為默認配置被其它找不到匹配的所有節點使用。
外部節點
有時候我們也許已經擁有了一些主機,它們正擔負着一定的任務,例如LDAP,版本控制,或者數據庫。這時候我們需要傳遞一些變量給這些節點。
這個時候,就需要在節點定義的時候編寫一些"外部節點"腳本。更多信息參閱相關章節。
我們已經講述了諸如排序和分組這樣的特性,現在來學習一些更多的內容。
Puppet不是一門編程語言,它以模型的方式描述我們的IT架構。這就是我們要做到全部事情,避免了要寫大量的編程代碼。
引號
大部分時候,不需要用引號將字符串引起來。任何以字母開頭的字母數字字符串(連字符也是允許的)都可以省略引號。
變量插入
到目前為止,我們在自定義資源類型中用到了變量。如果要在字符串中使用這些變量,必須使用雙引號,不能用單引號。單引號字符串不會解析其中的變量(interpolation),雙引號可以。字符串中的變量也可以使用{}括起來,這樣子更容易理解:
$value = "${one}${two}"
雙引號字符串中的引號("或')和美元符號($)有特殊的意義,若要把它們放到雙引號字符串中,必須在前面加轉義字符(\)。如果要將轉義字符(\)放到雙引號中,需要這樣用:\\.
單引號字符串只支持兩種轉義序列:單引號(\')和反斜杠(\\)。除此之外,單引號里面的任何字符都可以直接處理。
建議不需要變量插入的字符串都使用單引號,需要使用變量插入的時候才使用雙引號。"Style Guide"會詳細的討論這些,包含例子。
大寫
資源大寫有三種主要使用方式:
引用:當需要引用一個已經定義的資源,通常是為了依賴另外一個資源,我們就必須大寫資源名:
require => File['sshdconfig']
繼承:在一個資源子類中覆蓋父類的定義,需要大寫資源名。如果使用小寫就會出現一個錯誤的結果。
設置默認值:使用沒有標題的大寫資源,實際上是為該類資源設置默認值。先前的例子就為需要執行的命令設置了默認的path。
注意自定義資源類型中命名空間,需要大寫所有部分,例如Apache::Vhost['wordpress']。
數組
在前面的類和資源例子中有提到,Puppet在很多情況下都允許使用數組。數組的定義方式如下:
[ 'one', 'two', 'three' ]
可以使用索引存取數組中的某個項目:
$foo = [ 'one', 'two', 'three' ] notice $foo[1]
有幾個屬性成員,例如'host'中的'alias',就可以接受數組作為它們的值。擁有多個別名的host資源象下面的這樣:
host { 'one.example.com': ensure => present, alias => [ 'satu', 'dua', 'tiga' ], ip => '192.168.100.1', }
這段代碼會將主機'one.example.com'加入到主機列表,該主機擁有三個別名:'satu','dua'和'tiga'。
如果一個資源依賴多個其它資源,也可以使用數組按下面的方式定義:
resource { 'baz': require => [ Package['foo'], File['bar'] ], }
另一個使用數組的例子是多次調用一個自定義資源類型,例如:
define php::pear() { package { "php-${name}": ensure => installed } } php::pear { ['ldap', 'mysql', 'ps', 'snmp', 'sqlite', 'tidy', 'xmlrpc']: }
當然,上面的使用方法也適用於內置資源類型:
file { [ 'foo', 'bar', 'foobar' ]: owner => 'root', group => 'root', mode => '0600', }
哈希
從2.6.0開始,Puppet開始支持哈希功能。Puppet哈希定義類似Ruby:
{ key1 => val1, key2 => val2, ... }
哈希鍵是字符串,但是值可以是任何可能的RHS值,例如函數調用,變量,等等。
可以分配一個變量給哈希:
$myhash = { key1 => 'myval', key2 => $b }
遞歸的存取哈希成員(這也適用於數組):
$myhash = { key => { subkey => 'b' }} notice($myhash[key][subkey])
可以用哈希成員作為資源標題,或者參數的缺省定義,或者資源的參數。
變量
Puppet也象大多數我們熟悉的編程語言一樣支持變量。Puppet變量用美元符號($)標明:
$content = 'some content\n' file { '/tmp/testing': content => $content }
Puppet語言是一門定義語言,意思是說它的范圍和分配規則很多時候和通常的命令語言是不同的。主要的不同體現在一個范圍內我們不能改變變量的值,因為那依賴與語句在文件中的順序才能決定它的值。在定義語言中順序是不重要的。
$user = root file { '/etc/passwd': owner => $user, } $user = bin file { '/bin': owner => $user, recurse => true, }
不能為變量重新賦值,替換的處理方式是使用內置條件:
$group = $operatingsystem ? { solaris => 'sysadmin', default => 'wheel', }
一個范圍中,每個變量只能被賦值一次。當然,我們可以在非重疊范圍為同一個變量賦另外的值。例如,設置頂級配置:
node a { $setting = 'this' include class_using_setting } node b { $setting = 'that' include class_using_setting }
上面的例子,節點"a"和節點"b"有不同的范圍,這不是為同一個變量重新賦值。
變量范圍
范圍規定了變量在什么地方有效。Unlike early programming languages like BASIC, variables are only valid and accessible in certain places in a program. 同一個變量名在不同的范圍,不是指向同一個值。
類和節點開始一個新范圍。Puppet現在動態的定義范圍,也就是說創建范圍層次結構是依據代碼執行的地方,而不是代碼定義的地方。
Variable Scope
Scoping may initially seem like a foreign concept, though in reality it is pretty simple. A scope defines where a variable is valid. Unlike early programming languages like BASIC, variables are only valid and accessible in certain places in a program. Using the same variable name in different parts of the language do not refer to the same value.
Classes and nodes introduce new scopes. Puppet is currently dynamically scoped, which means that scope hierarchies are created based on where the code is evaluated instead of where the code is defined.
For example:
$test = 'top'
class myclass {
exec { "/bin/echo ${test}": logoutput => true }
}
class other {
$test = 'other'
include myclass
}
include other
In this case, there’s a top-level scope, a new scope for other
, and the a scope below that for myclass
. When this code is evaluated, $test
evaluates to other
, not top
.
Qualified Variables
Puppet supports qualification of variables inside a class. This allows you to use variables defined in other classes.
For example:
class myclass {
$test = 'content'
}
class anotherclass {
$other = $myclass::test
}
In this example, the value of the $other
variable evaluates to content
. Qualified variables are read-only — you cannot set a variable’s value from other class.
Variable qualification is dependent on the evaluation order of your classes. Class myclass
must be evaluated before class anotherclass
for variables to be set correctly.
Facts as Variables
In addition to user-defined variables, the facts generated by Facter are also available as variables. This allows values that you would see by running facter
on a client system within Puppet manifests and also within Puppet templates. To use a fact as a variable prefix the name of the fact with $
. For example, the value of the operatingsystem
and puppetversion
facts would be available as the variables$operatingsystem
and $puppetversion
.
Variable Expressions
In Puppet 0.24.6 and later, arbitrary expressions can be assigned to variables, for example:
$inch_to_cm = 2.54
$rack_length_cm = 19 * $inch_to_cm
$gigabyte = 1024 * 1024 * 1024
$can_update = ($ram_gb * $gigabyte) > 1 << 24
See the Expression section later on this page for further details of the expressions that are now available.
Appending to Variables
In Puppet 0.24.6 and later, values can be appended to array variables:
$ssh_users = [ 'myself', 'someone' ]
class test {
$ssh_users += ['someone_else']
}
Here the $ssh_users
variable contains an array with the elements myself
and someone
. Using the variable append syntax, +=
, we added another element, someone_else
to the array.
Please note, variables cannot be modified in the same scope because of the declarative nature of Puppet. As a result, $ssh_users contains the element ‘someone_else’ only in the scope of class test and not outside scopes. Resources outside of this scope will “see” the original array containing only myself and someone.