運維學習筆記-看看別人家的Puppet代碼

這篇博客的目的是通過分析Forge上的Puppet模塊來加深一些概念的理解,同時了解一些常用用法。

今天的例子是jfryman-nginx模塊,它是原puppetlabs-nginx模塊的升級版本,依賴3個Puppet公共模塊:puppetlabs-aptpuppetlabs-stdlibpuppetlabs-concat。安裝非常方便,puppet module install會自動為你安裝所依賴的模塊。

> puppet module install jfryman-nginx
/etc/puppet/modules
└─┬ jfryman-nginx (v0.3.0)
     ├── puppetlabs-apt (v2.2.2)                        #Puppet公共模塊,提供Debain/Ubuntu下安裝包管理功能
     ├── puppetlabs-stdlib (v4.12.0)                   #Puppet公共模塊,提供對各種數據類型的操作函數,比如檢查,轉換之類
     └── puppetlabs-concat (v2.1.0)                  #Puppet公共模塊,可以將分散在不同文件中的內容組合到一個目標文件中

模塊的目錄結構就不在這里累述了,tree /etc/puppet/modules/nginx命令可以顯示詳細的結構,需要注意的是模塊的manifests目錄中有以下5個文件:

├── manifests
│   ├── init.pp                             #模塊自裝載起始文件
│   ├── params.pp                      #參數類定義文件,用于給其他各類的參數變量提供默認值
│   ├── config.pp                        #配置類的定義文件
│   ├── package.pp                    #軟件包安裝類的定義文件
│   ├── service.pp                      #服務類的定義文件
│   └── ...

任何Forge中的模塊,基本都會包含上面這幾個文件。這已經成為了一種標準。其好處之一是可以清晰劃分從軟件包安裝,配置到服務啟動的各階段功能;另一方面代碼中可以利用類來定義各階段的依賴關系,因為每個階段通常都包含多個資源,所以用類定義依賴關系比用資源更加靈活實用。


下面我們看看模塊的init.pp

代碼片段1:類定義


class nginx (                                                      #nginx類定義
...
  $package_name      = $::nginx::params::package_name,    #用nginx::params類中的變量$package_name給nginx類的$package_name賦默認值
  $package_source    = 'nginx',                           #設置$package_source默認值為'nginx'
  $package_flavor    = undef,                             #設置$package_source默認值undef
...
) inherits ::nginx::params {                                  #nginx類繼承nginx::params類

以上是nginx的類定義。不禁要問幾個問題

nginx類為什么要定義參數

主要是為了使nginx類更加靈活的適應各種不同的應用場景。調用者可以通過nginx類的參數變量來傳遞不同的值,從而影響nginx中資源的屬性和行為。

nginx為什么要繼承nginx::params類

在Puppet中,類繼承一般只用于2個目的

    * 重用基類中大部分代碼和邏輯,只重寫一小部分代碼以實現新的功能

    * 利用基類中的預定義變量值給子類的類參數賦值。

這里是第2種情況。nginx會用到的大多數默認值都預定義在nginx::params類中。nginx繼承nginx::params類來為自己的參數賦默認值。這也是最常用的賦默認值的方式。

這里專門定義了nginx::params類是因為某些默認值很可能會隨目標主機的OS或其他Facts有所差異,所以將相關邏輯隔離在nginx::params類中可以更容易管理代碼。比如下面的代碼片段是nginx::params類中根據Facts $::osfamily設置hash變量$_module_os_overrides

  case $::osfamily {
    'ArchLinux': {
      $_module_os_overrides = {
        'pid'         => false,
        'daemon_user' => 'http',
      }
    }
    'FreeBSD': {
      $_module_os_overrides = {
        'conf_dir'    => '/usr/local/etc/nginx',
        'daemon_user' => 'www',
        'root_group'  => 'wheel',
      }
    }
...
}

很容易聯想到的另一個場景是為類的參數賦初值(注意不是賦默認值),它需要在聲明類的時候(注意不是定義類的時候使用resource-like聲明方式聲明類。比如

  class { '::nginx::service':
    configtest_enable => $configtest_enable,            #用變量$configtest_enable為類參數賦初值
....
  }

類的名字和繼承有什么關系?

答案是沒有關系。確定繼承關系的唯一標準是看類定義時是否使用了inherits關鍵字。類的名字只和其manifest文件在模塊目錄結構中的位置有關。比如: 詳情請看模塊的目錄結構

/etc/puppet/modules/nginx/manifests
├── init.pp                                                #nginx類
├── package.pp                                       #nginx::package類
├── package
│   ├── debian.pp                                     #nginx::package::debian類
│   └── redhat.pp                                     #nginx::package::redhat類
...

什么時候應該使用變量的長名字,什么時候使用短名字?

使用短名字訪問變量受scope(作用域)的限制。

scope-euler-diagram.png

這張圖中有4層作用域

    a. 頂層作用域是top scope,包含Facts和Agent/Master內置變量,以及所有site.pp中節點定義以外的所有變量,表達式,資源類型等等內容

    b. 下面一層是node scope, 也就是site.pp中節點定義所包含的所有內容

    c. 再下面是各種類的定義,在圖中包括example:parent,example:four和exmaple:other.這3個類在同一層,但每個類自己是一個單獨的作用域。

    d. 最下面一層是example:child,它是example:parent的子類,自己是一個作用域。

底層作用域中的代碼可以用變量的短名字訪問上層作用域的變量。比如,top scope 有個變量$var, example:child可以直接在自己的代碼中用短名字$var使用它,同樣方法也可以用來訪問node scope和它的父類example:parent中的變量。

注意:這么做的前提條件是本地作用域沒有同名的變量。在上面的代碼段中,雖然nginx是nginx:params的子類,但因為都定義了同名的變量$package_nam,需要用長名字$::nginx:params::package_name指代nginx:params類中的$package_name變量。

除此之外,只要是訪問其他作用域里的變量,都必須用長名字。比如訪問同一個模塊中的其他類的變量,或者另外一個模塊里的的類變量。來看幾個例子:   

    a. example::four和example:child在一個模塊中,但它既不是example:child的父類,也不是node scope或者top scope, 如果想訪問其中的變量,需要這樣寫

include ::exmaple::four
$myvar=$::example::four::var1。

  b. 我想訪問apache模塊中的apache::php類里的變量$var1。需要這樣寫

include ::apache::php
$myvar=$::apache::ph::var1。

在實際的編碼中,為了清晰,一般都會在名字左邊加::,表示從頂層作用域開始唯一標識一個類或者變量。這就就如同文件路徑中的絕對路徑,不會造成任何混淆。

代碼片段2:依賴關系定義


Class['::nginx::package'] -> Class['::nginx::config'] ~> Class['::nginx::service']

這段代碼的意思是nginx::package類必須在nginx::config類之前執行,而nginx::service類必須在nginx::config類之后執行。而且nginx::config類中任何資源的狀態發生變化,比如文件內容,nginx::service類中的所有資源都會收到refresh事件。

值得注意的幾點是

為什么需要描述依賴關系?

Puppet語言是聲明性語言,不描述流程,所以Puppet代碼執行的順序和代碼寫的順序經常是不一致的,這就是Puppet中經常提到的 independent of evaluation-order 。當資源或者類有執行的先后順序時,就需要顯性的描述依賴關系。

還有什么方式可以描述依賴關系?

類和資源都可以使用->和~>描述依賴關系(資源與資源,類與類,資源與類之間)。

另一種方法是用元參數require/before/notify/subscribe。這種方法可以在資源聲明時說明依賴關系,既可以用于資源,也適用于resource-like聲明的類。

還有一種方法是使用require函數(注意不是元參數require)描述類之間的依賴關系。詳情請見在線文檔

為什么Class首字母大寫?

因為是引用(reference),也就是在定義和聲明之外的任何場景使用類或者資源時,Class和資源類型關鍵字的首字母都要大寫。

常見的引用場景包括

    a. 類或資源聲明時,使用require/before/notify/subscribe來描述依賴關系,比如

file { '/etc/nginx.conf'
require=> Package['nginx']                                #Package首字母大寫
}

    b. 資源聲明時,引用另外一個資源的屬性,這個例子中引用另外一個文件資源的mode屬性

file { "/etc/second.conf":
ensure => file,
mode   => File["/etc/first.conf"]["mode"]                #File首字母大寫
}

    c. 資源聲明后,需要設置或者修改資源的某個屬性,比如

File['/etc/nginx.conf'] {                                 #File首字母大寫
content => template('nginx/sample.conf'),
}

在引用資源時,如果被引用的資源類型是長名字時(一般是自定義資源類型),所有::分隔的命名空間的首字母都要大寫,比如。

Nginx::Resource::Location["${name}-default"] {           #Nginx::Resource::Location各段首字母大寫
    location_cfg_prepend => $location_cfg_prepend
}

引用的對象是誰?

類,資源及屬性可以被引用。變量不適用。

可以在類或者資源聲明前引用他們嗎?

答案是可以,可以在類或者資源聲明前引用他們。此外,由于在同一個catalog范圍的所有資源(標題)和類(名字)必須是唯一的,所以可以在代碼的任何部分引用任何類和資源,不受作用域影響。

哪些資源可以處理refresh事件

service, mount和exec資源可以處理refresh事件

service的默認行為是使用init腳本(redhat linux上,腳本在/etc/rc.d/init.d/中)重新啟動服務,如果你想避免重啟服務, 可以設置restart屬性

service {"sshd":
restart=>"service reload sshd"                #收到refresh時,運行reload而不是restart
}

mount的默認行為是重新掛載(umount再mount)

exec的默認行為是重新運行命令。它有兩個相關的屬性

exec { "/bin/ls ":
refresh   => "ls -l"                             #refresh發生時,運行refresh屬性指定的另外一個命令
refreshonly => true,                          #exec只在refresh事件發生時才運行命令
}

代碼片段3:調用所需的類


class { '::nginx::service':
    configtest_enable => $configtest_enable,
    service_ensure    => $service_ensure,
    service_restart   => $service_restart,
    service_name      => $service_name,
    service_flags     => $service_flags,
}

這段代碼是用resource-like方式聲明類。

Puppet支持兩種類的聲明方式

  include-like方式:使用include, require, contain或者hiera_include關鍵字,后面跟類的名字來聲明類

  resource-like方式:像聲明資源一樣聲明類。

定義和聲明有什么區別?

以類舉例,定義一般是說明類看起來是什么樣子,含有那些資源,聲明是設定類參數從而確定類中資源的屬性和行為,說明類的依賴關系,然后告訴Puppet在catalog中加入這個類的一個實例。

除了類,自定義的資源類型,函數,Facts,Provider等等也需要先定義,然后聲明。

注意:變量不需要定義或者聲明,直接賦值就可以了

inlcude-like和resource-like聲明類有什么區別?

相比inlcude-like,resource-like最大優勢是可以為類賦值,這也是在類聲明時為類傳遞參數的唯一方法。在上面的例子中,=>左邊是類參數,右邊是參數值。

同時,resource-like也有一個缺點,就是只能聲明一次。相比之下,include-like可以聲明任意多次。在一個catalog范圍內,允許把個resource-like(一次)和include-like(多次)混合使用。

為什么resource-like聲明只允許使用一次呢?

OOP中的類的通常有下面4個特性。Puppet中的類不一樣,只有前3個特性,且只支持從一個父類繼承,不支持從多個父類繼承。

            抽象

            封裝

            繼承

            多態性

原因是Puppet中的類只是一般OOP中的的單實例類(singleton)。

當用include-like方式聲明類時,雖然聲明了多次,但是在catalog中只會有一個類的實例,Puppet也只會執行這個實例一次。這就是所謂的“可以多次聲明,但只應用一次”。因為include-like不傳入任何參數,所以這個單實例可滿足所有調用者的要求。

resource-like聲明可以傳入參數,理論上講,傳入不同的參數也就創建出不同的實例。 所以為了保證一個實例,resource-like聲明只允許使用一次,只生成一個實例。

在include-like聲明方式中,include, require, contain和hiera_include有什么區別嗎?

include關鍵是最簡單的聲明方式,就是告訴Puppet在catalog中生成一個類的實例。

require關鍵字除了include的功能,還表明的類的依賴關系

hiera_include關鍵字是在當Master和Hirea集成時使用,它可以通過Hirea獲取類的信息并聲明。

contain用在比較特殊的場合。我們會在下面和anchor一起解釋。

代碼片段4:使用anchor


anchor{ 'nginx::begin':
    before => Class['::nginx::package'],
    notify => Class['::nginx::service'],
}
anchor { 'nginx::end':
    require => Class['::nginx::service'],
}

anchor是Puppet的內置資源類型。上面的代碼聲明了兩個anchor資源,分別是nginx::begin和nginx::end,他們將類nginx::package,nginx::config和nginx::service夾在中間(這3個類在上面已經用->/~>設好了依賴關系),作用是強制這3個類在nginx類開始后執行,并且必須在nginx類退出前全部執行完。

上面的代碼等價于

anchor{ 'nginx::begin': }
anchor{ 'nginx::end': }
Anchor['nginx::begin']->Class['::nginx::package']->Class['::nginx::config']->Class['::nginx::service']->Anchor['nginx::end']

為什么需要用anchor呢?

如果代碼中只有2層類,比如類A包含類A1和類A2,A1和A2都直接包含資源,且A2依賴A1,那么執行A時Puppet會按照定義好的順序先執行A1內的資源,再執行A2里的資源。

當類的層次增加時,情況就不同了。比如這個例子

# /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp
class profiles::dbserver {
 include mysql
}
# /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp
class profiles::webserver {
 include apache
}
# /etc/puppetlabs/puppet/modules/roles/webstack.pp
class roles::ecommerce_app {
 include profiles::dbserver
 include profiles::webserver
 Class['profiles::dbserver'] -> Class['profiles::webserver']
}
#/etc/puppetpabs/puppet/manifests/site.pp
node 'webapp01.puppetlabs.com' {
 include roles::ecommerce_app
}

大多數人的第一感覺上是Puppet會先運行mysql類,然后是apache類,實際結果卻不一定,經常會看到相反的順序,這是因為默認情況下,下層的類(mysql類和apache類)之間不會繼承上層類(profiles::dbserver類和profiles::webserver類)之間的依賴關系。

為了使下層的類也能夠遵循上層類的順序執行,需要使用contain或者anchor

a. 使用contain

# /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp
class profiles::dbserver {
  contain mysql                                 #把include換成contain
}
# /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp
class profiles::webserver {
  contain apache                                #把include換成contain
}

b.使用anchor

# /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp
class profiles::dbserver {
  anchor{'before_mysql:'} -> class{'mysql':} -> anchor{'after_mysql':}   #聲明anchor并把mysql夾在中間
}
# /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp
class profiles::webserver {
  anchor{'before_apache:'} -> class{'apache':} -> anchor{'after_apache':}#聲明anchor并把apache夾在中間
}

anchor和contain有什么區別呢?

contain和anchor的效果完全相同。Puppet也同時支持這兩種方法。

區別是contian是在Puppet Enterprise 3.2.0 (Puppet 3.4.0)之后才出現的,在此之前只能用anchor. 而且contain后面只能直接跟類的名字,不能跟resource-like類聲明。

除此之外,還可以看到一些常用的用法

代碼片段5:調用stdlib函數


  validate_string($multi_accept)                        #檢查輸入字符串是否合法
...
  validate_array($proxy_set_header)                #檢查輸入數組是否合法
...
  validate_bool($confd_purge)                         #檢查輸入bool是否合法
...

上面的代碼是調用puppetlabs-stdlib模塊中攜帶的函數做各種檢查。

puppetlabs-stdlib是Forge上的一個公共模塊,提供了很多自定義資源類型,函數,Facts,極大的方便了編程。在編程中也非常常用到。

在使用時,需要先確認puppetlabs-stdlib已經安裝在你的系統中(可以用puppet module list檢查),如果沒有安裝,運行puppet module install puppetlabs-stdlib進行安裝。

一般情況下,無需顯性聲明stdlib(include stdlib),可以直接調用其中功能,和使用內置的資源類型,函數,Facts沒有區別。

代碼片段6:調用concat函數


concat { $config_file:                                    #聲明concat資源,指定目標文件,這里是$config_file指代的文件
    owner  => $owner,
    group  => $group,
    mode   => $mode,
    notify => Class['::nginx::service'],
}

concat::fragment { "${name_sanitized}-header":           #聲明concat::fragment資源,然后將所需內容寫入目標文件。
    target  => $config_file,                             #目標文件是$config_file指代的文件
    content => template('nginx/vhost/vhost_header.erb'), #寫入的內容來自于模板'nginx/vhost/vhost_header.erb'
    order   => '001',                                    #說明當前內容在目標文件中的位置。這個數字越小,寫入的內容越排在前面
}

concat::fragment { "${name_sanitized}-footer":            #聲明另外一個concat::fragment資源,然后將所需內容寫入目標文件。  
    target  => $config_file,                              #目標文件是$config_file指代的文件
    content => template('nginx/vhost/vhost_footer.erb'),  #寫入的內容來自于模板'nginx/vhost/vhost_footer.erb'
    order   => '699',                                     #說明當前內容在目標文件中的位置。699大于001,所以寫在vhost_header.erb寫在vhost_header.erb
}

以上代碼是將使用puppetlabs-concat模塊將vhost_header.erb和vhost_footer.erb的結果輸出的$config_file指定的文件中。

puppetlabs-concat也是一個很常用的公共模塊,它的作用是將不同的文件的內容排序后輸出到一個新的目標文件中。使用puppetlabs-concat與puppetlabs-stdlib方法一致,不再累述。

代碼片段7:日志信息函數


warning('$worker_processes must be an integer or have value "auto".')                #輸出一條警告信息

除了notify資源,Puppet還有很多內置的函數可以輸出不同級別的日志信息,非常方便。

        emerg             #emergency level

        crit                  #critical level

        alert                #alert level

        err                  #error level

        warning          #warning level

        info                #information level

        debug            # debug level

        notice            # notice level

代碼片段8:模板


<%- if @listen_ip.is_a?(Array) then -%>                                 #判斷listen_ip是不是一個列表(array),listen_ip是外部的變量,所以有@
   <%- @listen_ip.each do |ip| -%>                                         #遍歷listen_ip列表。ip代表列表中的當前項,是內部變量,所以他沒有@
 listen       <%= ip %>:<%= @listen_port %><% if @listen_options %> <%= @listen_options %><% end %>;    #將內部變量ip, 外部變量listen_port 和listen_options組成一行,寫入文件。
   <%- end -%>
 <%- else -%>                                                                        #如果listen_ip只有一個值,不是列表,就不再遍歷
 listen       <%= @listen_ip %>:<%= @listen_port %><% if @listen_options %> <%= @listen_options %><% end %>; ##將外部變量listen_ip,listen_port 和listen_options組成一行,寫入文件。
   <%- end -%>
<%- end -%>

上面是模板中的一段代碼,由ERB(embedded ruby )寫成,基本覆蓋了比較典型的邏輯和語法。詳情請參照在線文檔

原創文章,作者:MVP,如若轉載,請注明出處:http://www.www58058.com/18103

(0)
MVPMVP
上一篇 2016-06-23
下一篇 2016-06-23

相關推薦

  • 樹 非線性結構 樹是n(n >= 0)個元素的集合: (1)每個元素稱為結點(node); (2)有一個特定的結點,稱為根結點或根(root); (3)除根結點外,其余結點被分成m(m>=0)個互不相交的有限集合,而每個子集又都是一棵樹(稱為原樹的子樹Subtree) 注意 n = 0時,稱為空樹 樹只有一個特殊的沒有前驅的元素,稱為樹的根(Ro…

    2017-10-16
  • 馬哥教育網絡班21期-第六周課程練習

    請詳細總結vim編輯器的使用并完成以下練習題1、復制/etc/rc.d/rc.sysinit文件至/tmp目錄,將/tmp/rc.sysinit文件中的以至少一個空白字符開頭的行的行首加#; #cp /etc/rc.d/rc.sysinit /tmp #vim /tmp/rc.sysinit :%s/^[[:space:]]/#…

    Linux干貨 2016-08-15
  • linux網絡屬性管理

    Linux網絡屬性配置 計算機網絡:TCP/IP:協議棧(使用)ISO,OSI:協議棧(學習) MAC:Media Access Control48bits:ICANN:24bits, 2^24地址塊:2^24 網橋(bridge):MAC地址表靜態指定:動態學習:根據原地址學習; 交換機(switch):多端口網橋; IP(Internet protoco…

    Linux干貨 2017-10-14
  • llinux常用命令及bash基本特性

    一、常用的文件和目錄管理命令 1. pwd命令:用來顯示當前的工作目錄 語法格式:直接輸入pwd回車顯示當前的工作目錄 示例:用pwd命令顯示當前的工作目錄 [root@suyiwen ~]# pwd /root 2. mkdir命令:用來創建目錄文件 語法格式:mkdir [OPTION]… DIRECTORY…常用option: -m,用來指定目錄的權限…

    Linux干貨 2018-03-11
  • shell 腳本的編輯基礎

          shell腳本是Linux運維工程師必須掌握的技能之一,shell腳本的使用讓我們更好的操作Linux系統,方便了我們的執行。 一,編程基礎 編程基本概念 編程邏輯處理方式:順序執行,循環執行,選擇執行 程序:指令+ 數據 shell 編程:過程式、解釋執行 shell程序:提供了編程能力,解釋執…

    Linux干貨 2016-08-22
  • N26-第十三周

    1、建立samba共享,共享目錄為/data,要求:(描述完整的過程)  1)共享名為shared,工作組為magedu;  2)添加組develop,添加用戶gentoo,centos和ubuntu,其中gentoo和centos以develop為附加組,ubuntu不屬于develop組;密碼均為用戶名;  3)添加samb…

    Linux干貨 2017-06-01
欧美性久久久久