Puppet 是一個設定管理系統。它用於使主機進入所需狀態並維持該狀態。
我與 Puppet 合作已經五年多了。 本文本質上是對官方文件中的要點進行翻譯和重新排序的彙編,這將讓初學者快速理解 Puppet 的本質。
基本信息
Puppet 的作業系統是客戶端-伺服器,儘管它也支援功能有限的無伺服器操作。
使用拉操作模型:預設情況下,客戶端每半小時聯繫伺服器一次以取得配置並套用它。 如果您使用過 Ansible,那麼他們會使用不同的推播模型:管理員啟動應用程式設定的過程,客戶端本身不會套用任何內容。
網路通訊時,採用雙向TLS加密:伺服器和用戶端都有自己的私鑰和對應的憑證。 通常,伺服器會為客戶端頒發證書,但原則上可以使用外部 CA。
宣言簡介
在 Puppet 術語中 到傀儡伺服器 連接 節點 (節點)。 節點的配置已寫入 在宣言中 使用特殊的程式語言 - Puppet DSL。
Puppet DSL 是一種聲明性語言。 它以各個資源聲明的形式描述節點的所需狀態,例如:
- 該文件存在並且具有特定內容。
- 該軟體包已安裝。
- 服務已開始。
資源可以互聯:
- 存在依賴關係,它們影響資源的使用順序。
例如,“首先安裝包,然後編輯配置文件,然後啟動服務。” - 有通知 - 如果資源發生更改,它會向訂閱它的資源發送通知。
例如,如果設定檔發生更改,您可以自動重新啟動服務。
此外,Puppet DSL 還具有函數和變量,以及條件語句和選擇器。 也支援各種模板機制 - EPP 和 ERB。
Puppet 是用 Ruby 寫的,因此許多結構和術語都取自 Ruby。 Ruby 可讓您擴展 Puppet - 新增複雜的邏輯、新類型的資源、功能。
當 Puppet 執行時,伺服器上每個特定節點的清單都會編譯到一個目錄。 Каталог 是計算函數、變數和條件語句擴展的值後的資源及其關係的列表。
語法和代碼風格
如果提供的範例還不夠,以下是官方文件的部分內容,可協助您理解文法:
以下是清單的範例:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}
縮排和換行不是清單的必需部分,但建議這樣做
- 不使用兩個空格縮排、製表符。
- 大括號以空格分隔;冒號不以空格分隔。
- 每個參數後面都加逗號,包括最後一個。每個參數都位於單獨的行上。沒有參數和一個參數的情況是例外:您可以寫在一行上且不帶逗號(即:
resource { 'title': }
иresource { 'title': param => value }
). - 參數上的箭頭應處於同一水平。
- 前面寫著資源關係箭頭。
pappetserver 上檔案的位置
為了進一步解釋,我將引入「根目錄」的概念。 根目錄是包含特定節點的 Puppet 配置的目錄。
根目錄會根據 Puppet 版本和使用的環境而有所不同。 環境是儲存在單獨目錄中的獨立配置集。 通常與 git 結合使用,在這種情況下,環境是從 git 分支創建的。 因此,每個節點位於一種環境或另一種環境中。 這可以在節點本身上配置,也可以在 ENC 中配置,我將在下一篇文章中討論。
- 在第三個版本(“舊 Puppet”)中,基本目錄是
/etc/puppet
。 環境的使用是可選的 - 例如,我們不將它們與舊的 Puppet 一起使用。 如果使用環境,它們通常儲存在/etc/puppet/environments
,根目錄將是環境目錄。如果不使用環境,根目錄將是基底目錄。 - 從第四個版本(“新 Puppet”)開始,環境的使用成為強制性的,並且基本目錄被移動到
/etc/puppetlabs/code
。 因此,環境儲存在/etc/puppetlabs/code/environments
,根目錄是環境目錄。
根目錄下必須有子目錄 manifests
,其中包含一個或多個描述節點的清單。 另外,還應該有一個子目錄 modules
,其中包含模組。 稍後我會告訴你哪些模組。 另外,舊的Puppet可能還有一個子目錄 files
,其中包含我們複製到節點的各種檔案。 在新的 Puppet 中,所有檔案都放置在模組中。
清單檔案的副檔名 .pp
.
幾個戰鬥例子
節點及其資源的描述
在節點上 server1.testdomain
必須建立一個文件 /etc/issue
有內容 Debian GNU/Linux n l
。 該文件必須由使用者和群組擁有 root
,存取權限必須是 644
.
我們寫下宣言:
node 'server1.testdomain' { # блок конфигурации, относящийся к ноде server1.testdomain
file { '/etc/issue': # описываем файл /etc/issue
ensure => present, # этот файл должен существовать
content => 'Debian GNU/Linux n l', # у него должно быть такое содержимое
owner => root, # пользователь-владелец
group => root, # группа-владелец
mode => '0644', # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
}
}
節點上資源之間的關係
在節點上 server2.testdomain
nginx 必須正在運行,並使用先前準備好的設定。
我們來分解一下問題:
- 需要安裝該套件
nginx
. - 需要從伺服器複製設定檔。
- 該服務需要運行
nginx
. - 如果更新配置,則必須重新啟動服務。
我們寫下宣言:
node 'server2.testdomain' { # блок конфигурации, относящийся к ноде server2.testdomain
package { 'nginx': # описываем пакет nginx
ensure => installed, # он должен быть установлен
}
# Прямая стрелка (->) говорит о том, что ресурс ниже должен
# создаваться после ресурса, описанного выше.
# Такие зависимости транзитивны.
-> file { '/etc/nginx': # описываем файл /etc/nginx
ensure => directory, # это должна быть директория
source => 'puppet:///modules/example/nginx-conf', # её содержимое нужно брать с паппет-сервера по указанному адресу
recurse => true, # копировать файлы рекурсивно
purge => true, # нужно удалять лишние файлы (те, которых нет в источнике)
force => true, # удалять лишние директории
}
# Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
# подписаться на изменения ресурса, описанного выше.
# Волнистая стрелка включает в себя прямую (->).
~> service { 'nginx': # описываем сервис nginx
ensure => running, # он должен быть запущен
enable => true, # его нужно запускать автоматически при старте системы
}
# Когда ресурс типа service получает уведомление,
# соответствующий сервис перезапускается.
}
為此,您大約需要 puppet 伺服器上的以下檔案位置:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.conf
資源類型
可以在此處找到支援的資源類型的完整列表
文件
管理文件、目錄、符號連結、其內容和存取權限。
選項:
- 資源名稱 — 檔案路徑(可選)
- 路徑 — 檔案路徑(如果名稱中未指定)
- 保證 - 文件類型:
absent
- 刪除一個文件present
— 必須有任何類型的文件(如果沒有文件,將建立常規文件)file
- 常規文件directory
- 目錄link
- 符號連結
- 內容 — 文件內容(僅適用於常規文件,不能與 資源 或 目標)
- 資源 — 指向要從中複製文件內容的路徑的連結(不能與 內容 或 目標)。 可以指定為帶有方案的 URI
puppet:
(然後將使用來自 puppet 伺服器的檔案),並使用該方案http:
(我希望清楚在這種情況下會發生什麼),即使有圖表file:
或作為沒有模式的絕對路徑(然後將使用節點上本地 FS 中的檔案) - 目標 — 符號連結應指向的位置(不能與 內容 或 資源)
- 業主 — 應該擁有該文件的用戶
- 組 — 文件應屬於的群組
- 模式 — 檔案權限(作為字串)
- 遞歸 - 啟用遞迴目錄處理
- 清除 - 允許刪除 Puppet 中未描述的文件
- 強制 - 允許刪除 Puppet 中未描述的目錄
包
安裝和移除軟體包。能夠處理通知 - 如果指定了參數,則重新安裝套件 刷新時重新安裝.
選項:
- 資源名稱 — 套件名稱(可選)
- 名稱 — 套件名稱(如果名稱中未指定)
- 提供者 — 使用的套件管理器
- 保證 — 所需的包裝狀態:
present
,installed
- 安裝的任何版本latest
- 安裝最新版本absent
- 已刪除(apt-get remove
)purged
— 與設定檔一起刪除(apt-get purge
)held
- 軟體包版本已鎖定(apt-mark hold
)любая другая строка
— 指定版本已安裝
- 刷新時重新安裝 - 如果
true
,然後在收到通知後將重新安裝該軟體包。 對於基於原始程式碼的發行版非常有用,在這種發行版中,更改建置參數時可能需要重新建置套件。 預設false
.
服務
管理服務。 能夠處理通知 - 重新啟動服務。
選項:
- 資源名稱 — 要管理的服務(可選)
- 名稱 — 需要管理的服務(如果名稱中沒有指定)
- 保證 — 期望的服務狀態:
running
- 推出stopped
- 停止了
- 使 — 控制啟動服務的能力:
true
— 自動運行已啟用(systemctl enable
)mask
- 偽裝(systemctl mask
)false
— 自動運行已停用(systemctl disable
)
- 重新開始 - 重新啟動服務的命令
- 狀態 — 檢查服務狀態的命令
- 已重新啟動 — 指示服務 initscript 是否支援重新啟動。 如果
false
並且指定了參數 重新開始 — 使用此參數的值。 如果false
和參數 重新開始 未指定 - 服務已停止並開始重新啟動(但 systemd 使用命令systemctl restart
). - 狀態 — 指示服務初始化腳本是否支援該命令
status
。 如果false
,然後使用參數值 狀態。 預設true
.
EXEC
運行外部命令。 如果不指定參數 創建, 除非, 除非 或 僅刷新,每次執行 Puppet 時都會執行該指令。可以處理通知 - 運行命令。
選項:
- 資源名稱 — 要執行的命令(可選)
- 命令 — 要執行的命令(如果名稱中未指定)
- 路徑 — 尋找可執行檔的路徑
- 除非 — 如果此參數中指定的命令以零返回代碼完成,則將執行主命令
- 除非 — 如果此參數中指定的命令以非零返回碼完成,則將執行主命令
- 創建 — 如果該參數指定的檔案不存在,則執行主命令
- 僅刷新 - 如果
true
,那麼只有當此 exec 收到來自其他資源的通知時才會執行該命令 - 西德 — 執行指令的目錄
- 用戶 — 運行命令的用戶
- 提供者 - 如何運行命令:
- POSIX — 簡單地建立了一個子進程,請務必指定 路徑
- 殼 - 這個指令在 shell 中啟動
/bin/sh
, 可以不指定 路徑,您可以使用通配符、管道和其他 shell 功能。 通常會自動偵測是否有任何特殊字元(|
,;
,&&
,||
等等)。
cron的
控制 cronjobs。
選項:
- 資源名稱 - 只是某種標識符
- 保證 — 皇冠工作狀態:
present
- 如果不存在則創建absent
- 如果存在則刪除
- 命令 - 要運行什麼命令
- 環境 — 在哪個環境中執行指令(環境變數及其值的列表,透過
=
) - 用戶 — 從哪個使用者執行命令
- 分鐘, 小時, 平日, 月, 月日 — 何時運行 cron。 如果未指定任何這些屬性,則其在 crontab 中的值將為
*
.
在木偶 6.0 中 cron的 彷彿
關於一般資源
資源唯一性要求
我們最常遇到的錯誤是 重複申報。 當目錄中出現兩個或多個同類型同名資源時,會發生此錯誤。
因此,我再寫一次: 同一節點的清單不應包含具有相同標題的相同類型的資源!
有時需要安裝具有相同名稱但使用不同套件管理器的套件。 在這種情況下,您需要使用參數 name
避免錯誤:
package { 'ruby-mysql':
ensure => installed,
name => 'mysql',
provider => 'gem',
}
package { 'python-mysql':
ensure => installed,
name => 'mysql',
provider => 'pip',
}
其他資源類型有類似的選項來幫助避免重複 - name
у 服務, command
у EXEC, 等等。
元參數
每種資源類型都有一些特殊參數,無論其性質為何。
元參數的完整列表
短名單:
- 要求 — 此參數表示該資源依賴哪些資源。
- 之前 - 此參數指定哪些資源依賴於該資源。
- 訂閱 — 此參數指定該資源從哪些資源接收通知。
- 通知 — 此參數指定哪些資源接收來自該資源的通知。
所有列出的元參數都接受單一資源連結或方括號中的連結數組。
資源連結
資源連結只是對資源的提及。 它們主要用於指示依賴關係。 引用不存在的資源將導致編譯錯誤。
連結的語法如下:資源類型使用大寫字母(如果類型名稱包含雙冒號,則冒號之間的名稱各部分均大寫),然後是方括號中的資源名稱(名稱的大小寫不會改變!)。 不應有空格;方括號緊接在類型名稱之後。
示例:
file { '/file1': ensure => present }
file { '/file2':
ensure => directory,
before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']
依賴項和通知
如前所述,資源之間的簡單依賴關係是可傳遞的。 順便說一句,在新增依賴項時要小心 - 您可以建立循環依賴項,這將導致編譯錯誤。
與依賴關係不同,通知是不可傳遞的。 以下規則適用於通知:
- 如果資源收到通知,則會更新。 更新操作取決於資源類型 - EXEC 運行命令, 服務 重新啟動服務, 包 重新安裝該軟體包。 如果資源沒有定義更新操作,則不會發生任何事情。
- 在 Puppet 的一次運作期間,資源更新不會超過一次。 這是可能的,因為通知包含依賴關係並且依賴關係圖不包含循環。
- 如果 Puppet 更改資源的狀態,則該資源會向訂閱它的所有資源發送通知。
- 如果資源更新,它會向訂閱該資源的所有資源發送通知。
處理未指定的參數
通常,如果某些資源參數沒有預設值,且清單中未指定該參數,則 Puppet 不會變更節點上對應資源的該屬性。 例如,如果類型的資源 文件 未指定參數 owner
,那麼Puppet不會改變對應文件的擁有者。
類別、變數和定義簡介
假設我們有幾個節點,它們具有相同的配置部分,但也存在差異 - 否則我們可以在一個區塊中描述所有內容 node {}
。當然,您可以簡單地複製配置的相同部分,但通常這是一個糟糕的解決方案 - 配置會增長,當您更改配置的一般部分時,您將不得不在許多地方編輯相同的內容。同時,很容易犯錯,一般來說,DRY(不要重複自己)原則的發明是有原因的。
為了解決這個問題有這樣的設計 類.
類
首先需要描述類別。描述本身不會在任何地方添加任何資源。該類別在其清單中進行了描述:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}
之後可以使用該類別:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше
上一個任務的範例 - 讓我們將 nginx 的安裝和設定移到一個類別中:
class nginx_example {
package { 'nginx':
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => 'puppet:///modules/example/nginx-conf',
recure => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
include nginx_example
}
變量
上一個範例中的類別根本不靈活,因為它總是帶來相同的 nginx 設定。 讓我們將路徑設定為配置變量,然後該類別可用於以任何配置安裝 nginx。
可以辦到
注意:Puppet 中的變數是不可變的!
另外,變數只有在宣告後才能訪問,否則變數的值將被 undef
.
使用變數的範例:
# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"
傀儡有 命名空間,相應地,變數有 視野範圍:同名變數可以定義在不同的命名空間。 解析變數的值時,將在目前命名空間中搜尋該變量,然後在封閉的命名空間中搜索,依此類推。
命名空間範例:
- 全域 - 類別或節點描述之外的變數位於此處;
- 節點描述中的節點命名空間;
- 類別描述中的類別命名空間。
為了避免存取變數時出現歧義,可以在變數名稱中指定命名空間:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var
我們同意 nginx 配置的路徑位於變數中 $nginx_conf_source
。 然後類別將如下所示:
class nginx_example {
package { 'nginx':
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => $nginx_conf_source, # здесь используем переменную вместо фиксированной строки
recure => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
$nginx_conf_source = 'puppet:///modules/example/nginx-conf'
include nginx_example
}
然而,給出的例子很糟糕,因為有一些“秘密知識”,即類別中的某個地方使用了具有這樣那樣名稱的變數。 讓這些知識變得普遍化是更正確的-類別可以有參數。
類別參數 是類別命名空間中的變量,它們在類別頭中指定,並且可以像類別主體中的常規變數一樣使用。 參數值是在使用清單中的類別時指定的。
此參數可以設定為預設值。 如果一個參數沒有預設值且使用時沒有設定該值,就會導致編譯錯誤。
讓我們對上面範例中的類別進行參數化,並添加兩個參數:第一個參數是必需的,是配置的路徑,第二個參數是可選的,是nginx 的套件的名稱(例如,在Debian 中,有包 nginx
, nginx-light
, nginx-full
).
# переменные описываются сразу после имени класса в круглых скобках
class nginx_example (
$conf_source,
$package_name = 'nginx-light', # параметр со значением по умолчанию
) {
package { $package_name:
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => $conf_source,
recurse => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
# если мы хотим задать параметры класса, функция include не подойдёт* — нужно использовать resource-style declaration
# *на самом деле подойдёт, но про это расскажу в следующей серии. Ключевое слово "Hiera".
class { 'nginx_example':
conf_source => 'puppet:///modules/example/nginx-conf', # задаём параметры класса точно так же, как параметры для других ресурсов
}
}
在 Puppet 中,變數是類型化的。 吃
類型寫在參數名稱之前:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}
類別:包括類別名稱 vs 類別{'classname':}
每個類別都是一個類型的資源 類。 與任何其他類型的資源一樣,同一節點上不能有同一類別的兩個實例。
如果您嘗試使用以下命令將類別新增至同一節點兩次 class { 'classname':}
(沒有差別,參數不同或相同),會出現編譯錯誤。 但如果您使用資源樣式的類,則可以立即在清單中明確設定其所有參數。
但是,如果您使用 include
,那麼可以根據需要多次添加該類別。 事實是 include
是一個冪等函數,用於檢查類別是否已添加到目錄中。 如果該類別不在目錄中,則會將其添加,如果已經存在,則不執行任何操作。 但如果使用 include
您無法在類別宣告期間設定類別參數 - 所有必要的參數必須在外部資料來源 - Hiera 或 ENC 中設定。 我們將在下一篇文章中討論它們。
定義
如上一個區塊所述,同一個類別不能在一個節點上出現多次。 但是,在某些情況下,您需要能夠在同一節點上使用具有不同參數的相同程式碼區塊。 換句話說,需要一種自己的資源類型。
例如,為了安裝 PHP 模組,我們在 Avito 中執行以下操作:
- 安裝包含此模組的軟體包。
- 讓我們為此模組建立一個設定檔。
- 我們建立 php-fpm 配置的符號連結。
- 我們為 php cli 建立一個指向配置的符號連結。
在這種情況下,諸如 $title
,資源名稱在聲明時的位置。 就像類別的情況一樣,必須先描述定義,然後才能使用它。
帶有 PHP 模組的簡化範例:
define php74::module (
$php_module_name = $title,
$php_package_name = "php7.4-${title}",
$version = 'installed',
$priority = '20',
$data = "extension=${title}.son",
$php_module_path = '/etc/php/7.4/mods-available',
) {
package { $php_package_name:
ensure => $version,
install_options => ['-o', 'DPkg::NoTriggers=true'], # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
}
-> file { "${php_module_path}/${php_module_name}.ini":
ensure => $ensure,
content => $data,
}
file { "/etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
ensure => link,
target => "${php_module_path}/${php_module_name}.ini",
}
file { "/etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
ensure => link,
target => "${php_module_path}/${php_module_name}.ini",
}
}
node server3.testdomain {
php74::module { 'sqlite3': }
php74::module { 'amqp': php_package_name => 'php-amqp' }
php74::module { 'msgpack': priority => '10' }
}
捕獲重複聲明錯誤的最簡單方法是在 Define 中。 如果定義具有具有常數名稱的資源,並且在某個節點上有該定義的兩個或多個實例,則會發生這種情況。
保護自己免受這種情況的影響很容易:定義中的所有資源都必須有一個名稱,這取決於 $title
。 另一種方法是冪等添加資源;在最簡單的情況下,將定義的所有實例共有的資源移動到單獨的類別中並將該類別包含在定義中就足夠了 - 函數 include
冪等的。
新增資源時還有其他方法可以實現冪等性,即使用函數 defined
и ensure_resources
,但我會在下一集中告訴你。
類別和定義的依賴關係和通知
類別和定義新增以下規則來處理依賴項和通知:
- 對類別/定義的依賴會增加對該類別/定義的所有資源的依賴;
- 類別/定義依賴項將依賴項新增至所有類別/定義資源;
- class/define通知通知該class/define的所有資源;
- class/define 訂閱訂閱該 class/define 的所有資源。
條件語句和選擇器
if
這裡很簡單:
if ВЫРАЖЕНИЕ1 {
...
} elsif ВЫРАЖЕНИЕ2 {
...
} else {
...
}
除非
except 是 if 的倒轉:如果表達式為 false,則會執行程式碼區塊。
unless ВЫРАЖЕНИЕ {
...
}
手機殼
這裡也沒有什麼複雜的。 您可以使用正規值(字串、數字等)、正規表示式和資料類型作為值。
case ВЫРАЖЕНИЕ {
ЗНАЧЕНИЕ1: { ... }
ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
default: { ... }
}
選擇器
選擇器是一種類似 case
,但它不是執行程式碼區塊,而是傳回一個值。
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }
模塊
當配置較小時,可以輕鬆地將其保存在清單中。 但是我們描述的配置越多,清單中的類別和節點就越多,它就會成長,並且使用起來變得不方便。
此外,還有程式碼重用的問題——當所有程式碼都在一個清單中時,很難與其他人共用此程式碼。 為了解決這兩個問題,Puppet 有一個稱為模組的實體。
模塊 - 這些是放置在單獨目錄中的類別、定義和其他 Puppet 實體的集合。 換句話說,模組是 Puppet 邏輯的一個獨立部分。 例如,可能有一個用於與 nginx 一起工作的模組,並且它將包含並且僅包含與 nginx 一起工作所需的內容,或者可能有一個用於與 PHP 一起工作的模組,等等。
模組是有版本的,並且還支援模組之間的依賴關係。 有一個開放的模組存儲庫 -
在puppet伺服器上,模組位於根目錄的modules子目錄中。 每個模組內部都有一個標準的目錄方案 - 清單、檔案、範本、lib 等等。
模組中的檔案結構
模組的根目錄可能包含以下具有描述性名稱的目錄:
manifests
- 它包含宣言files
- 它包含文件templates
- 它包含模板lib
— 它包含 Ruby 程式碼
這不是目錄和文件的完整列表,但目前對於本文來說已經足夠了。
模組中的資源名稱和檔案名稱
模組中的資源(類別、定義)不能隨意命名。 此外,資源名稱與 Puppet 將在其中尋找該資源描述的檔案名稱之間存在直接對應關係。 如果違反命名規則,Puppet 根本找不到資源描述,並且會出現編譯錯誤。
規則很簡單:
- 模組中的所有資源都必須位於模組命名空間中。 如果模組被調用
foo
,那麼其中的所有資源都應該被命名foo::<anything>
, 要不就foo
. - 具有模組名稱的資源必須位於檔案中
init.pp
. - 對於其他資源,文件命名方案如下:
- 模組名稱的前綴被丟棄
- 所有雙冒號(如果有)都替換為斜杠
- 新增了擴展名
.pp
我將用一個例子來演示。 假設我正在編寫一個模組 nginx
。 它包含以下資源:
- 類
nginx
清單中所描述的init.pp
; - 類
nginx::service
清單中所描述的service.pp
; - 定義
nginx::server
清單中所描述的server.pp
; - 定義
nginx::server::location
清單中所描述的server/location.pp
.
模板
想必大家都知道什麼是模板,這裡就不詳細介紹了。 但我會留下它以防萬一
如何使用模板:可以使用函數擴充模板的含義 template
,它傳遞到模板的路徑。 對於類型資源 文件 與參數一起使用 content
。例如,像這樣:
file { '/tmp/example': content => template('modulename/templatename.erb')
檢視路徑 <modulename>/<filename>
暗示文件 <rootdir>/modules/<modulename>/templates/<filename>
.
此外,還有一個功能 inline_template
— 它接收範本文字作為輸入,而不是檔案名稱。
在範本內,您可以使用目前範圍內的所有 Puppet 變數。
Puppet 支援 ERB 和 EPP 格式的範本:
簡單介紹一下ERB
控制結構:
<%= ВЫРАЖЕНИЕ %>
— 插入表達式的值<% ВЫРАЖЕНИЕ %>
— 計算表達式的值(不插入它)。 條件語句 (if) 和迴圈 (each) 通常放在這裡。<%# КОММЕНТАРИЙ %>
ERB 中的表達式是用 Ruby 寫的(ERB 其實是嵌入式 Ruby)。
要從清單存取變量,您需要添加 @
到變數名。 若要刪除出現在控制項結構之後的換行符,您需要使用結束標記 -%>
.
使用模板的範例
假設我正在編寫一個模組來控制 ZooKeeper。 負責創建配置的類別看起來像這樣:
class zookeeper::configure (
Array[String] $nodes,
Integer $port_client,
Integer $port_quorum,
Integer $port_leader,
Hash[String, Any] $properties,
String $datadir,
) {
file { '/etc/zookeeper/conf/zoo.cfg':
ensure => present,
content => template('zookeeper/zoo.cfg.erb'),
}
}
以及對應的模板 zoo.cfg.erb
- 所以:
<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>
dataDir=<%= @datadir %>
<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>
事實和內建變量
配置的具體部分通常取決於節點上目前發生的情況。 例如,根據 Debian 版本,您需要安裝一個或另一個版本的軟體包。 您可以手動監控所有這些,如果節點發生變化則重寫清單。 但這不是一個嚴肅的方法;自動化要好得多。
為了獲取有關節點的信息,Puppet 有一種稱為事實的機制。 事實 - 這是有關節點的信息,在清單中以全域命名空間中的普通變數的形式提供。 例如,主機名稱、作業系統版本、處理器架構、使用者清單、網路介面及其位址清單等等。 事實可作為常規變數在清單和範本中使用。
使用事實的範例:
notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог
從形式上來說,事實有一個名稱(字串)和一個值(可以使用多種類型:字串、陣列、字典)。 吃
在操作過程中,puppet 代理程式首先將所有可用的事實收集器從 pappetserver 複製到節點,然後啟動它們並將收集到的事實傳送到伺服器; 此後,伺服器開始編譯目錄。
可執行檔形式的事實
這些事實被放置在目錄中的模組中 facts.d
。 當然,這些文件必須是可執行的。 運行時,它們必須以 YAML 或 key=value 格式將資訊輸出到標準輸出。
不要忘記,這些事實適用於由部署模組的 poppet 伺服器控制的所有節點。 因此,在腳本中,請注意檢查系統是否具有事實運作所需的所有程式和檔案。
#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'
紅寶石的事實
這些事實被放置在目錄中的模組中 lib/facter
.
# всё начинается с вызова функции Facter.add с именем факта и блоком кода
Facter.add('ladvd') do
# в блоках confine описываются условия применимости факта — код внутри блока должен вернуть true, иначе значение факта не вычисляется и не возвращается
confine do
Facter::Core::Execution.which('ladvdc') # проверим, что в PATH есть такой исполняемый файл
end
confine do
File.socket?('/var/run/ladvd.sock') # проверим, что есть такой UNIX-domain socket
end
# в блоке setcode происходит собственно вычисление значения факта
setcode do
hash = {}
if (out = Facter::Core::Execution.execute('ladvdc -b'))
out.split.each do |l|
line = l.split('=')
next if line.length != 2
name, value = line
hash[name.strip.downcase.tr(' ', '_')] = value.strip.chomp(''').reverse.chomp(''').reverse
end
end
hash # значение последнего выражения в блоке setcode является значением факта
end
end
文字事實
這些事實被放置在目錄中的節點上 /etc/facter/facts.d
在舊木偶或 /etc/puppetlabs/facts.d
在新的木偶中。
examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue
了解事實
有兩種方法可以了解事實:
- 透過字典
$facts
:$facts['fqdn']
; - 使用事實名稱作為變數名稱:
$fqdn
.
最好用字典 $facts
,或者更好的是,指示全域名稱空間($::facts
).
內建變數
除了事實之外,還有
- 可信的事實 — 從用戶端證書中取得的變數(由於證書通常是在 poppet 伺服器上頒發的,因此代理程式不能只取得並更改其證書,因此這些變數是「受信任的」):證書的名稱、證書的名稱主機和網域,憑證的副檔名。
- 伺服器事實 —與伺服器資訊相關的變數—版本、名稱、伺服器IP位址、環境。
- 代理事實 — 由 puppet-agent 直接新增的變量,而非事實 — 憑證名稱、代理程式版本、puppet 版本。
- 主變數 - Pappetmaster 變數(原文如此!)。 與以下內容大致相同 伺服器事實,加上配置參數值可用。
- 編譯器變數 — 每個範圍內不同的編譯器變數:目前模組的名稱和存取目前物件的模組的名稱。 例如,它們可用於檢查您的私人類別是否未被其他模組直接使用。
添加1:如何運行和調試這一切?
文章中包含了許多puppet程式碼的例子,但根本沒有告訴我們如何運行這段程式碼。嗯,我正在糾正自己。
一個代理程式足以運行 Puppet,但在大多數情況下,您還需要一台伺服器。
代理人
至少從版本 XNUMX 開始,puppet-agent 軟體包來自
在最簡單的情況下,要使用 puppet 配置,以無伺服器模式啟動代理程式就足夠了:前提是將 puppet 程式碼複製到節點,啟動 puppet apply <путь к манифесту>
:
atikhonov@atikhonov ~/puppet-test $ cat helloworld.pp
node default {
notify { 'Hello world!': }
}
atikhonov@atikhonov ~/puppet-test $ puppet apply helloworld.pp
Notice: Compiled catalog for atikhonov.localdomain in environment production in 0.01 seconds
Notice: Hello world!
Notice: /Stage[main]/Main/Node[default]/Notify[Hello world!]/message: defined 'message' as 'Hello world!'
Notice: Applied catalog in 0.01 seconds
當然,最好設定伺服器並以守護程式模式在節點上執行代理程式 - 然後每半小時一次,它們將應用從伺服器下載的配置。
您可以模仿推送工作模式—前往您感興趣的節點並開始 sudo puppet agent -t
。 鑰匙 -t
(--test
)實際上包括幾個可以單獨啟用的選項。 這些選項包括以下內容:
- 不要以守護程式模式運行(預設情況下,代理程式以守護程式模式啟動);
- 應用目錄後關閉(預設情況下,代理將繼續工作並每半小時應用一次配置);
- 寫詳細的工作日誌;
- 顯示文件中的變更。
代理具有無需更改的操作模式 - 當您不確定是否編寫了正確的配置並想要檢查代理在操作期間究竟會更改什麼時,可以使用它。 此模式由參數啟用 --noop
在命令列上: sudo puppet agent -t --noop
.
此外,您可以啟用工作的偵錯日誌 - 在其中,puppet 會記錄它執行的所有操作:關於它目前正在處理的資源、關於該資源的參數、關於它啟動的程式。 當然這是一個參數 --debug
.
服務器
在本文中,我不會考慮 pappetserver 的完整設定以及向其部署程式碼;我只會說,有一個開箱即用的功能齊全的伺服器版本,不需要額外的配置即可與少量伺服器一起使用節點(例如,最多一百個)。 大量的節點將需要調整- 預設情況下,puppetserver 啟動不超過XNUMX 個工作進程,為了獲得更好的效能,您需要增加它們的數量,並且不要忘記增加記憶體限制,否則伺服器大部分時間都會進行垃圾收集。
程式碼部署 - 如果您快速輕鬆地需要它,請查看(r10k)[
附錄 2:編碼指南
- 將所有邏輯放在類別和定義中。
- 將類別和定義保留在模組中,而不是保留在描述節點的清單中。
- 使用事實。
- 不要根據主機名稱建立 if。
- 隨意為類別和定義添加參數 - 這比隱藏在類別/定義主體中的隱式邏輯更好。
我將在下一篇文章中解釋為什麼我建議這樣做。
結論
讓我們結束介紹。 在下一篇文章中我將向您介紹 Hiera、ENC 和 PuppetDB。
只有註冊用戶才能參與調查。
事實上,還有更多的材料 - 我可以就以下主題撰寫文章,並對您有興趣閱讀的內容進行投票:
- 企業排放佔全球 59,1%高級 puppet 構造 - 一些下一級的東西:循環、映射和其他 lambda 表達式、資源收集器、導出資源以及通過 Puppet、標籤、提供程序、抽象數據類型進行的主機間通信。13
- 企業排放佔全球 31,8%“我是我母親的管理員”,或者我們 Avito 如何與多個不同版本的 poppet 伺服器交朋友,原則上是有關管理 poppet 伺服器的部分。7
- 企業排放佔全球 81,8%我們如何寫傀儡程式碼:偵測、文件、測試、CI/CD.18
22 位用戶投票。 9 名用戶棄權。
來源: www.habr.com