[翻譯]用 Puppet 搭建易管理的伺服器基礎架構(3)

李勝攀發表於2015-08-01

我通過伯樂線上翻譯了一個Puppet簡明教程,一共分為四部分,這是第三部分。

 

本文由 伯樂線上 - Wing 翻譯,黃利民 校稿。未經許可,禁止轉載!
英文出處:Manuel Kiessling。歡迎加入翻譯組

 


關於

在《用 Puppet 搭建易管理的伺服器基礎架構(2)》中,我們在 Puppet master上編寫了第一個非常簡單的清單,來對puppetclient虛擬機器進行配置。現在我們要看一些更加複雜的配置,並且學習如何將清單組織成一個有用的結構。

模組

如果我們把伺服器基礎結構的那些可管理的配置一直存放在一個檔案中,一旦當基礎設施中的系統數目增加到一定程度時,就會不能很好擴充套件。對於一個基礎結構來說,它的幾個伺服器要滿足不同的角色,這樣基礎結構就會變得複雜。我們希望Puppet可以控制這種複雜局面。為了實現這些,我們需要保持清單中的資訊整潔有序。

一種方式是將清單分解到不同的模組裡。一個模組是一個或者多個清單的集合,這些清單放在一起,是因為它們有共同的用途。一個典型的模組示例是一個清單的集合,這些清單用來在目標機器上通過Apache HTTP伺服器來管理web頁面。這個模組會組裝一些清單,這些清單關注不同的配置點:軟體包安裝、配置檔案管理、服務管理。通過管理這些配置點,我們可以提供一個具有全部功能的web伺服器。

我們現在要建立這樣一個模組。一旦完成後,它會負責在puppetclient虛擬機器上提供一個完整的、可操作的web伺服器。

模組資料夾結構

從外部的角度來看,Puppet模組只是一些包含了特定檔案的資料夾結構。如果你把正確的檔案放到正確的資料夾結構中,那麼這個結構中的清單可以被內部其他清單所引用。

我們來演示這一點,將在第二部分中建立的第一個簡單清單中的相關部分移到模組中,然後我們會從主清單site.pp內部引用模組化的清單,而不是在site.pp內部直接建立清單宣告。

當然,我們第一個清單只是一個”Hello World”示例,因此,我們將模組命名為helloworld。

為了做到這一點,我們只需要在puppetserver虛擬機器上建立一個資料夾 /etc/puppet/modules/helloworld/manifests:

On the puppetserver VM

1
~# sudo mkdir -p /etc/puppet/modules/helloworld/manifests

除了清單本身,模組也可以在一個名為files的子目錄下擁有檔案,我們來建立這個目錄,然後將helloworld.txt檔案移動到這個目錄下:

On the puppetserver VM

1
2
~# sudo mkdir -p /etc/puppet/modules/helloworld/files
~# sudo mv /etc/puppet/files/helloworld.txt /etc/puppet/modules/helloworld/files/

現在我們需要為新模組建立主清單檔案,在/etc/puppet/modules/helloworld/manifests/init.pp中輸入以下內容:

/etc/puppet/modules/helloworld/manifests/init.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
11
class helloworld {
 
  file { "/home/ubuntu/helloworld.txt":
    ensure => file,
    owner  => "ubuntu",
    group  => "ubuntu",
    mode   => 0644,
    source => "puppet://puppetserver/modules/helloworld/helloworld.txt"
  }
 
}

如你所見,這基本上是我們最初的file配置塊,但是它現在被包在一個class塊內,而且helloworld.txt的路徑改變了。在Puppet清單中,class塊可以用來定義配置塊的地方,這樣後期可以在其他地方使用這些配置塊。file 塊並不是 node 定義的一部分,雖然我們把它定義成 node 的一部分,但它還是在那裡估算的。類本身並不是做任何事情,只有它在其它地方被宣告時,才會有意義。我們可以通過including語句來在node塊中宣告我們新定義的類:

/etc/puppet/manifests/site.pp on puppetserver

1
2
3
4
5
node "puppetclient" {
 
  include helloworld
 
}

我們的節點定義現在不直接執行配置塊,而只需要引用我們在helloworld類中定義的配置塊,這樣我們的節點也是新的helloworld模組中的一部分。將helloworld相關的配置都放到一個模組中,給我們帶來了非常大的靈活性。設想一下我們有一個包含了成百上千個節點的叢集,這些節點都需要接收helloworld配置塊,我們可以通過複製-貼上配置塊的方式來實現,但這樣在擴充套件方面不是特別好。然而,helloworld模組是可重用的,任意多數目的節點都可以使用它。

現在我們已經重新組織了現有的配置設定結構,它沒有改變實際的配置目錄,但是,當我們在puppetclient虛擬機器上再次執行代理的話,可以看到:

On the puppetclient VM

1
2
3
4
5
~# sudo puppet agent --verbose --no-daemonize --onetime
 
info: Caching catalog for puppetclient
info: Applying configuration version '1396287576'
notice: Finished catalog run in 0.05 seconds

因為實際的狀況和期望的配置是一致的,所以代理不會採取任何行動。

我們的新的清單在/etc/puppet目錄下,新的目錄結構如下所示。

1
2
3
4
5
6
7
8
9
10
11
files
 
manifests
  site.pp
 
modules
  helloworld
    manifests
      init.pp
    files
      helloworld.txt

第一個真實的模組:apache2

關於那些枯燥的Hello World示例,我已經談了足夠多的內容了,現在讓我們在puppetclient客戶端建立一個真實的web伺服器吧。

我們首先來重新命名helloworld模組, 因為我們已經不再需要它了。我們將會使用apache2 Ubuntu包來安裝web伺服器,這樣我們也可以來呼叫新的apache2模組:

On the puppetserver VM

1
~# sudo mv /etc/puppet/modules/helloworld /etc/puppet/modules/apache2

一個包含了全部功能的apache2 Puppet模組需要處理下面幾個方面:它必須保證某些軟體包被安裝了;它必須處理一些配置檔案;它必須保證http守護程式服務在執行(當伺服器的配置無論何時發生變化,這個程式都會被重啟)。

一個好訊息是Puppet模組在將來會被拆分成多個類。這樣可以幫助我們很清晰的分離關注點。這種分離並不是Puppet本身所要求的,從長遠來看,這樣做可以讓我們更容易的管理模組。

我們要談論上一個模組中已有的init.pp清單檔案,我們在/etc/puppet/modules/apache2/manifest目錄下建立清單檔案,檔名是install.pp。在這個檔案裡,我們會來處理一些軟體包的安裝工作。

/etc/puppet/modules/apache2/manifests/install.pp on puppetserver

1
2
3
4
5
6
7
class apache2::install {
 
  package { [ "apache2-mpm-prefork", "apache2-utils" ]:
    ensure => present
  }
 
}

我們這裡會引入一個新的塊型別:package。我們幾乎可以它被使用的方式來讀出它的目的:我們使用它來保證在目標系統中包括apache2-mpm-prefork包和apache-utils包。

另外,請注意對類進行命名的新語法,我們使用兩個冒號來表示類結構中的名稱空間。這個模組中的主類是apache2(我們會在init.pp清單檔案中使用這個類名),其他的類會使用apache2名字空間。我們可以隨便來為這些類命名——對於用來處理安裝問題的清單來說,install可能是一個明智的選擇。

就像之前解釋的那樣,Puppet為我們做了所有的重活,並利用apt-get來實現我們的期望,安裝哪些有問題的軟體包。

實際上,如果你想要的是一個執行的web伺服器,那麼安裝這些軟體包就足夠了,但我們會再向前一步,我們會處理web伺服器上的配置資訊。

模板和變數

為了實現這一點,我們來建立一個新的類:apache::config,這個類位於/etc/puppet/modules/apache2/manifest/config.pp檔案中:

/etc/puppet/modules/apache2/manifests/config.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
class apache2::config {
 
  file { "/etc/apache2/sites-available/${hostname}.conf":
    mode    => 0644,
    owner   => "root",
    group   => "root",
    content => template("apache2/etc/apache2/sites-available/vhost.conf.erb")
  }
 
}

這看上去和我們之前遇到的file塊有點兒像,但還是有一些明顯的區別的。它包括了兩個新內容:變數和模板(我們後面還會有模板中的變數)。

變數是一個強大的工具,我們可以用它來編寫更加智慧的清單。如你所見,檔案路徑通過hostname變數被引數化了,這個變數用來解析目標節點的完全限定域名字(full qualified domain name),對於我們的puppetclient虛擬機器來說,主機名字當然就是puppetclient。

On the puppetclient VM

1
2
3
~# hostname
 
puppetclient

這以為這當我們在目標節點上執行清單檔案時,它會建立一個名為/etc/apache2/sites-available/puppetclient.conf檔案。

Puppet自己可以決定很多變數的值,hostname只是其中一個,稍後我們會在這個系列中看到如何定義我們自己的變數。

在file塊中另外一個區別是content語句,它和我們之前遇到的source語句的不同之處在於,它不會指向一個靜態檔案,而是一個內嵌的ruby模板。對於靜態檔案,它只是從puppet master傳輸到客戶端,而對於模板檔案來說,它是動態的,可以包含變數和語句,這些變數會被替換成對應的值,而語句則會被執行。

我們可以在puppetserver虛擬機器上檢視一下/etc/puppet/modules/apache2/templates/etc/apache2/sites-available/vhost.conf.erb檔案,以便了解模板檔案的結構:

/etc/puppet/modules/apache2/templates/etc/apache2/sites-available/vhost.conf.erb on puppetserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<VirtualHost *:80>
  ServerName <%= hostname %>
 
  ServerAdmin webmaster@<%= hostname %>
 
  DocumentRoot /var/www
 
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
 
  <Directory /var/www/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>
 
  ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
  <Directory "/usr/lib/cgi-bin">
    AllowOverride None
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    Order allow,deny
    Allow from all
  </Directory>
 
  ErrorLog ${APACHE_LOG_DIR}/<%= hostname %>.error.log
  LogLevel warn
 
  CustomLog ${APACHE_LOG_DIR}/<%= hostname %>.access.log combined
</VirtualHost>

如你所見,這個檔案基本上就是你在平時常見的Apache虛擬機器檔案,但其中包含了一些佔位符。在這裡,我們只有一個佔位符:<%=hostname%>。當我們將這個檔案放到目標系統的目標檔案位置後,檔案中出現這個佔位符的每個地方都會被替換成目標系統的完全限定域名字。

我們可以先試著執行一下新的清單,但目前還有一些不完美的地方需要我們首先做一些調整。對於Puppet清單來說,一個問題是你不能夠真正預測清單塊中各個應用程式的順序。Puppet清單並不是批處理檔案——它們並不是簡單的按照從上到下的順序執行。每一個塊——就像檔案或者包——都有自己的順序。Puppet確實試著去決定一個有意義的執行順序,但這不是很完美的。對我們來說,如果執行順序很重要,那麼我們需要幫助Puppet來實現這一點。

對於我們這個簡單的apache2模組來說,在執行順序方面,可能會有什麼問題呢?好吧,我們安裝了一個軟體包,這個軟體包在安裝過程中會建立一個資料夾,我們要將一個檔案放到這個資料夾中。如果Puppet在資料夾被建立出來之前(資料夾通過安裝apache2-mpm-prefork包產生),就試著將虛擬機器檔案放到該資料夾中,那麼就會發生一個錯誤。

 

我們可以在清單中向Puppet解釋清單塊之間的依賴關係。為了說明針對虛擬機器檔案的file模組依賴於apache2-mpm-prefork安裝結束,我們可以向file塊中新增require語句:

/etc/puppet/modules/apache2/manifests/config.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
11
class apache2::config {
 
  file { "/etc/apache2/sites-available/${hostname}.conf":
    mode    => 0644,
    owner   => "root",
    group   => "root",
    content => template("apache2/etc/apache2/sites-available/vhost.conf.erb"),
    require => Package["apache2-mpm-prefork"]
  }
 
}

請注意,當我們引用一個包時,Package語句中的P應該是大寫的。

這樣,Puppet就可以知道如何決定清單的執行順序,它可以保證這樣的規則:只有在apache2-mpm-prefork被安裝到系統之後,才會執行file塊。

第一次測試執行

現在關於apache2模組的install和config清單,我們有了第一個可以工作的版本。現在我們需要保證這個模組是可以應用到puppetclient虛擬機器上的。為此,我們首先需要重寫模組中的主清單:

/etc/puppet/modules/apache2/manifests/init.pp on puppetserver

1
2
3
4
5
6
class apache2 {
 
  include apache2::install
  include apache2::config
 
}

然後,我們需要將新模組對映到puppetclient節點的/etc/puppet/manifests/site.pp中:

/etc/puppet/manifests/site.pp on puppetserver

1
2
3
4
5
node "puppetclient" {
 
  include apache2
 
}

On the puppetclient VM

1
2
3
4
5
6
7
8
~# sudo puppet agent --verbose --no-daemonize --onetime
 
info: Caching catalog for puppetclient
info: Applying configuration version '1396980729'
notice: /Stage[main]/Apache2::Install/Package[apache2-utils]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Apache2::Install/Package[apache2-mpm-prefork]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]/ensure: defined content as '{md5}c55c5bd945cea21c817bca1a465b7dd3'
notice: Finished catalog run in 16.62 seconds

好吧,現在可以工作了,但我們現在還沒有實現目標。puppetclient虛擬機器需要被啟用。為了實現這一點,只需要需要一個從 /etc/apache2/sites-enabled/puppetclient.conf 到 /etc/apache2/sites-available/puppetclient.conf的符號連結,我們可以建立另外一個file型別塊:

/etc/puppet/modules/apache2/manifests/config.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class apache2::config {
 
  file { "/etc/apache2/sites-available/${hostname}.conf":
    mode    => 0644,
    owner   => "root",
    group   => "root",
    content => template("apache2/etc/apache2/sites-available/vhost.conf.erb"),
    require => Package["apache2-mpm-prefork"]
  }
 
  file { "/etc/apache2/sites-enabled/${hostname}.conf":
    ensure  => link,
    target  => "/etc/apache2/sites-available/${hostname}.conf",
    require => File["/etc/apache2/sites-available/${hostname}.conf"]
  }
 
}

我們應用新的清單:

On the puppetclient VM

1
2
3
4
5
6
~# sudo puppet agent --verbose --no-daemonize --onetime
 
info: Caching catalog for puppetclient
info: Applying configuration version '1396983687'
notice: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-enabled/puppetclient.conf]/ensure: created
notice: Finished catalog run in 0.07 seconds

這樣我們就做完了,對嗎?好吧,從某種程度上說,是的。我們安裝了Apache,配置了虛擬主機。但是,如果使用Puppet,我們可以做得更好。如果手動管理puppetclient,我們會如何做呢?在修改了Apache配置後,我們會測試改動是否有任何錯誤(使用apachectl configtest),如果沒有錯誤,我們會重啟apache2服務。事實證明,Puppet也可以做到這一點。

改進模型

首先,我們需要讓Puppet學習apache2服務。為此,我們需要在puppetserver虛擬機器上建立另外一個清單檔案,這個檔案位於/etc/puppet/modules/apache2/manifests/service.pp

/etc/puppet/modules/apache2/manifests/service.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
11
12
class apache2::service {
 
  service { "apache2":
    ensure     => running,
    hasstatus  => true,
    hasrestart => true,
    restart    => "/usr/sbin/apachectl configtest && /usr/sbin/service apache2 reload",
    enable     => true,
    require    => Package["apache2-mpm-prefork"]
  }
 
}

如你所見,Puppet包括了service型別,可以用來處理守護(daemon)應用程式。在這個清單中,我們讓Puppet來控制apache2服務。無論何時Puppet代理在目標系統執行,它都會確保這個服務是在執行。同時,Puppet還可以重啟服務。在這種情況下,我們甚至可以教會Puppet只有在apachectl configtest執行沒有錯誤時才會重啟apache2服務。這樣,我們就可以實現自動化的服務管理,而且沒有放棄手動管理過程中的安全性檢查。

當然,我們需要在主清單檔案中宣告一個新類:

/etc/puppet/modules/apache2/manifests/init.pp on puppetserver

1
2
3
4
5
6
class apache2 {
 
  include apache2::install
  include apache2::config
include apache2::service
}

 

最後,因為無論何時修改虛擬主機的配置,我們都想觸發Apache重啟動作,所以我們需要將config清單和service清單連在一起,為此實現這一點,我們可以新增一個notify語句:

/etc/puppet/modules/apache2/manifests/config.pp on puppetserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class apache2::config {
 
  file { "/etc/apache2/sites-available/${hostname}.conf":
    mode    => 0644,
    owner   => "root",
    group   => "root",
    content => template("apache2/etc/apache2/sites-available/vhost.conf.erb"),
    require => Package["apache2-mpm-prefork"],
    notify  => Service["apache2"]
  }
 
  file { "/etc/apache2/sites-enabled/${hostname}.conf":
    ensure  => link,
    target  => "/etc/apache2/sites-available/${hostname}.conf",
    require => File["/etc/apache2/sites-available/${hostname}.conf"]
  }
 
}

現在我們看一下是否可以按照我們期望的那樣工作。可以簡單的修改一下位於/etc/puppet/modules/apache2/templates/etc/apache2/sites-available/vhost.conf.erb的虛擬主機模板檔案(新增一個空格或者空行就可以了),然後在puppetclient虛擬機器上重新執行代理,我已經高亮顯示了其中值得注意的地方:

On the puppetclient VM

1
2
3
4
5
6
7
8
9
10
~# sudo puppet agent --verbose --no-daemonize --onetime
 
info: Caching catalog for puppetclient
info: Applying configuration version '1396998784'
info: FileBucket adding {md5}c55c5bd945cea21c817bca1a465b7dd3
info: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]: Filebucketed /etc/apache2/sites-available/puppetclient.conf to puppet with sum c55c5bd945cea21c817bca1a465b7dd3
notice: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]/content: content changed '{md5}c55c5bd945cea21c817bca1a465b7dd3' to '{md5}afafea12b21e61c5e18879ce3fe475d2'
info: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]: Scheduling refresh of Service[apache2]
notice: /Stage[main]/Apache2::Service/Service[apache2]: Triggered 'refresh' from 1 events
notice: Finished catalog run in 0.32 seconds

我們也可以檢查試下安全機制是否正常工作。再一次編輯虛擬機器模板檔案,讓它變得不正確,例如,可以刪掉一個標籤的結束符>。

代理會像我們期望的那樣處理:

On the puppetclient VM

1
2
3
4
5
6
7
8
9
10
~# sudo puppet agent --verbose --no-daemonize --onetime
 
info: Caching catalog for puppetclient
info: Applying configuration version '1396998943'
info: FileBucket adding {md5}d15f727106b64c29d1c188efc9e6c97d
info: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]: Filebucketed /etc/apache2/sites-available/puppetclient.conf to puppet with sum d15f727106b64c29d1c188efc9e6c97d
notice: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]/content: content changed '{md5}d15f727106b64c29d1c188efc9e6c97d' to '{md5}7c86c7ba3cd4d7522d36b9d9fdcd46e2'
info: /Stage[main]/Apache2::Config/File[/etc/apache2/sites-available/puppetclient.conf]: Scheduling refresh of Service[apache2]
err: /Stage[main]/Apache2::Service/Service[apache2]: Failed to call refresh: Could not restart Service[apache2]: Execution of '/usr/sbin/apachectl configtest && /usr/sbin/service apache2 reload' returned 1:  at /etc/puppet/modules/apache2/manifests/service.pp:10
notice: Finished catalog run in 0.19 seconds

不要忘記修改這個模板檔案,這樣才能正常工作。

結論和展望

在使用了模板、變數、依賴以及通知後,構建一個成熟的、健壯的清單是非常直接和簡單的。使用模組以後,清單可以讓更廣闊的範圍內的客戶重用,在本系列的第4部分中,我們會討論引數化如何讓我們的模組更加靈活。

相關文章