О рецептах
Рецепт — это самый базовый элемент конфигурации в системе. Рецепт:
- Написан на языке Ruby, предназначенном для предсказуемого чтения и поведения.
- В основном представляет собой коллекцию ресурсов, определённых с помощью шаблонов (имена ресурсов, пары «атрибут-значение» и действия); вспомогательный код добавляется с помощью Ruby, когда это необходимо.
- Должен определять всё необходимое для конфигурации части системы.
- Должен храниться в кулинарной книге.
- Может быть включён в другой рецепт.
- Может использовать результаты запроса поиска и читать содержимое хранилища данных (включая зашифрованное хранилище данных).
- Может зависеть от одного (или нескольких) рецептов.
- Должен быть добавлен в список выполнения перед использованием Chef Infra Client.
- Всегда выполняется в том же порядке, что и в списке выполнения.
Атрибуты рецепта
Атрибут может быть определён в кулинарной книге (или рецепте) и затем использоваться для переопределения параметров по умолчанию на узле. При загрузке кулинарной книги во время выполнения Chef Infra Client эти атрибуты сравниваются с атрибутами, уже присутствующими на узле. Атрибуты, определённые в файлах атрибутов, загружаются сначала в соответствии с порядком кулинарных книг. Для каждой кулинарной книги атрибуты в файле default.rb загружаются первыми, а затем дополнительные файлы атрибутов (если они есть) загружаются в лексикографическом порядке. Когда атрибуты кулинарной книги имеют приоритет над атрибутами по умолчанию, Chef Infra Client применяет эти новые настройки и значения во время выполнения Chef Infra Client на узле.
Примечание
Переменные окружения
В UNIX, среда процесса — это набор пар «ключ-значение», доступных процессу. Программы ожидают, что их среда будет содержать информацию, необходимую для работы программы. Подробности о том, как получить доступ к этим парам «ключ-значение», зависят от API используемого языка.
Если процессы запускаются с помощью ресурсов execute или script (или любых ресурсов, основанных на этих двух ресурсах, таких как bash), используйте атрибут environment для изменения среды, которая будет передана процессу.
bash 'env_test' do
code <<-EOF
echo $FOO
EOF
environment ({ 'FOO' => 'bar' })
end
Изменяется только среда, передаваемая дочернему процессу, запущенному ресурсом bash. Это не повлияет на среду Chef Infra Client или дочерние процессы.
Работа с рецептами
В следующих разделах показаны подходы к работе с рецептами.
Использование хранилищ данных
Хранилища данных хранят глобальные переменные в формате JSON. Хранилища данных индексируются для поиска и могут быть загружены кулинарной книгой или использованы в запросе поиска.
Содержимое хранилища данных может быть загружено в рецепт. Например, хранилище данных с именем apps и элемент хранилища данных с именем my_app:
{
"id": "my_app",
"repository": "git://github.com/company/my_app.git"
}
может быть обработано в рецепте следующим образом:
my_bag = data_bag_item('apps', 'my_app')
Ключи и значения элемента хранилища данных можно получить с помощью хэша:
my_bag['repository'] #=> 'git://github.com/company/my_app.git'
Секретные ключи
Для шифрования элемента хранилища данных требуется секретный ключ. Секретный ключ может быть создан различными способами. Например, OpenSSL может быть использован для генерации случайного числа, которое затем может быть использовано в качестве секретного ключа:
openssl rand -base64 512 | tr -d '\r\n' > encrypted_data_bag_secret
где encrypted_data_bag_secret — имя файла, который будет содержать секретный ключ. Например, для создания секретного ключа с именем «my_secret_key»:
openssl rand -base64 512 | tr -d '\r\n' > my_secret_key
Команда tr удаляет все завершающие символы новой строки. Это предотвращает повреждение ключа при передаче файла между платформами с разными кодировками концов строк.
Хранение ключей на узлах
Ключ шифрования также может быть сохранён в альтернативном файле на узлах, которым он нужен, и путь к файлу может быть указан в атрибуте; однако EncryptedDataBagItem.load ожидает увидеть фактический секрет в качестве третьего аргумента, а не путь к секретному файлу. В этом случае можно использовать EncryptedDataBagItem.load_secret для загрузки содержимого секретного файла, а затем передать его:
# inside your attribute file:
# default[:mysql][:secretpath] = 'C:\\chef\\any_secret_filename'
#
# inside your recipe:
# look for secret in file pointed to by mysql attribute :secretpath
mysql_secret = Chef::EncryptedDataBagItem.load_secret('#{node['mysql']['secretpath']}')
mysql_creds = Chef::EncryptedDataBagItem.load('passwords', 'mysql', mysql_secret)
mysql_creds['pass'] # will be decrypted
Назначение зависимостей
Если кулинарная книга зависит от рецепта, расположенного в другой кулинарной книге, эта зависимость должна быть объявлена в файле metadata.rb для этой кулинарной книги с использованием ключевого слова depends.
Примечание
Например, если следующий рецепт включён в кулинарную книгу с именем my_app:
include_recipe 'apache2::mod_ssl'
то файл metadata.rb для этой кулинарной книги будет содержать:
depends 'apache2'
Включение рецептов
Рецепт может включать один (или несколько) рецептов из кулинарных книг, используя метод include_recipe. Когда рецепт включается, ресурсы, найденные в этом рецепте, будут вставлены (в том же самом порядке) в точку, где находится ключевое слово include_recipe.
Синтаксис включения рецепта выглядит следующим образом:
include_recipe 'recipe'
Например:
include_recipe 'apache2::mod_ssl'
Можно включать несколько рецептов в один рецепт. Например:
include_recipe 'cookbook::setup'
include_recipe 'cookbook::install'
include_recipe 'cookbook::configure'
Если один и тот же рецепт включён более одного раза с помощью метода include_recipe или где-либо ещё в run_list напрямую, будет обработана только первая инстанция, а последующие включения будут проигнорированы.
Перезагрузка атрибутов
Иногда атрибуты зависят от действий, выполняемых внутри рецептов, поэтому может потребоваться перезагрузить данный атрибут внутри рецепта. Например:
ruby_block 'some_code' do
block do
node.from_file(run_context.resolve_attribute('COOKBOOK_NAME', 'ATTR_FILE'))
end
action :nothing
end
Использование Ruby
Любое действие, которое можно выполнить с помощью Ruby, можно использовать внутри рецепта, например, выражения (if, unless и т. д.), операторы case, операторы цикла, массивы, хэши и переменные. В Ruby условные операторы nil и false ложны; все остальные условные операторы — true.
Присвоение значения
Переменная использует знак равенства (=) для присвоения значения.
Чтобы присвоить значение переменной:
package_name = 'apache2'
Использование оператора case
Оператор case может использоваться для сравнения выражения и затем выполнения соответствующего кода.
Для выбора имени пакета в зависимости от платформы:
package 'apache2' do
case node['platform']
when 'centos', 'redhat', 'fedora', 'suse'
package_name 'httpd'
when 'debian', 'ubuntu'
package_name 'apache2'
when 'arch'
package_name 'apache'
end
action :install
end
Проверка условий
Выражение if может использоваться для проверки условий (истина или ложь).
Для проверки условия только для платформ Debian и Ubuntu:
if platform?('debian', 'ubuntu')
# do something if node['platform'] is debian or ubuntu
else
# do other stuff
end
Выполнение условий
Выражение unless может использоваться для выполнения кода, когда условие возвращает ложное значение (по сути, выражение unless является противоположностью оператора if).
Для выполнения выражения, когда условие возвращает ложное значение:
unless node['platform_version'] == '5.0'
# do stuff on everything but 5.0
end
Перебор массива
Оператор цикла используется для выполнения блока кода один (или несколько) раз. Оператор цикла создаётся, когда .each добавляется к выражению, определяющему массив или хэш. Массив — это индексированная коллекция объектов. Каждый элемент в массиве может быть связан с индексом и к нему можно обратиться по индексу.
Для перебора массива имён пакетов в зависимости от платформы:
['apache2', 'apache2-mpm'].each do |p|
package p
end
Перебор хэша
Хэш — это коллекция пар «ключ-значение». Индексация хэша выполняется с использованием произвольных ключей любого типа (в отличие от индексации массива). Синтаксис хэша: key => "value".
Для перебора хэша имён пакетов gem:
{ 'fog' => '0.6.0', 'highline' => '1.6.0' }.each do |g, v|
gem_package g do
version v
end
end
Применение к спискам выполнения
Рецепт должен быть назначен в список выполнения с использованием соответствующего имени, определённого каталогом кулинарной книги и пространством имён. Например, каталог кулинарной книги имеет следующую структуру:
cookbooks/
apache2/
recipes/
default.rb
mod_ssl.rb
Есть два рецепта: рецепт по умолчанию (с таким же именем, как и кулинарная книга) и рецепт с именем mod_ssl. Синтаксис назначения рецепта в список выполнения аналогичен:
{
'run_list': [
'recipe[cookbook_name::default_recipe]',
'recipe[cookbook_name::recipe_name]'
]
}
где ::default_recipe подразумевается (и не нужно указывать). На узле эти рецепты могут быть назначены в список выполнения узла так:
{
'run_list': [
'recipe[apache2]',
'recipe[apache2::mod_ssl]'
]
}
Chef Infra Server
Используйте knife для добавления рецепта в список выполнения узла. Например:
knife node run list add NODENAME "recipe[apache2]"
Можно добавить больше одного рецепта:
% knife node run list add NODENAME "recipe[apache2],recipe[mysql],role[ssh]"
что создаёт список выполнения, похожий на:
run_list:
recipe[apache2]
recipe[mysql]
role[ssh]
chef-solo
Используйте файл JSON для передачи деталей списка выполнения в chef-solo, при условии, что кулинарная книга, в которой находится рецепт, доступна на системе, на которой работает chef-solo. Например, файл с именем dna.json содержит следующие данные:
{
"run_list": ["recipe[apache2]"]
}
Чтобы добавить список выполнения на узел, введите следующее:
sudo chef-solo -j /etc/chef/dna.json
Использование результатов поиска
Индексы поиска позволяют выполнять запросы для любого типа данных, индексированных Chef Infra Server, включая хранилища данных (и элементы хранилищ данных), среды, узлы и роли. Определённый синтаксис запроса поддерживает шаблоны поиска, такие как точные, с подстановочными знаками, диапазон и приближённый поиск. Поиск — это полнотекстовый запрос, который можно выполнить из нескольких мест, включая рецепт, с помощью подкоманды search в knife, метода search в языке Chef Infra, поля поиска в консоли управления Chef, а также с помощью конечных точек /search или /search/INDEX в API Chef Infra Server. Двигатель поиска основан на Elasticsearch и работает на сервере Chef Infra Server.
Результаты запроса поиска могут быть загружены в рецепт. Например, очень простой запрос поиска (в рецепте) может выглядеть так:
search(:node, 'attribute:value')
Запрос поиска можно присвоить переменным и затем использовать в другом месте рецепта. Например, чтобы найти все узлы, у которых есть назначение роли с именем webserver, а затем отобразить шаблон, включающий эти назначения ролей:
webservers = search(:node, 'role:webserver')
template '/tmp/list_of_webservers' do
source 'list_of_webservers.erb'
variables(webservers: webservers)
end
Использование тегов
Тег — это пользовательское описание, которое применяется к узлу. Применённый тег может быть полезным при управлении узлами с помощью инструмента knife или при построении рецептов, предоставляя альтернативные способы группирования похожих типов информации.
Теги можно добавлять и удалять. Можно проверить, имеют ли машины определённый тег. Чтобы использовать теги в рецепте, просто добавьте следующее:
tag('mytag')
Чтобы проверить, имеет ли машина тег, добавьте следующее:
tagged?('mytag')
чтобы вернуть true или false. tagged? также может использовать массив в качестве аргумента.
Чтобы удалить тег:
untag('mytag')
Например:
tag('machine')
if tagged?('machine')
Chef::Log.info("Hey I'm #{node['tags']}")
end
untag('machine')
unless tagged?('machine')
Chef::Log.info('I am not tagged')
end
Вернёт что-то вроде этого:
[Thu, 22 Jul 2010 18:01:45 +0000] INFO: Hey I'm machine
[Thu, 22 Jul 2010 18:01:45 +0000] INFO: I has no tagz
Завершение выполнения Chef Infra Client
Иногда может потребоваться остановить обработку рецепта и/или остановить всё выполнение Chef Infra Client. Существует несколько способов сделать это:
- Используйте ключевое слово
returnдля остановки обработки рецепта на основе условия, но продолжайте обработку выполнения Chef Infra Client - Используйте ключевое слово
raiseдля остановки выполнения Chef Infra Client, вызвав необработанное исключение - Используйте блок
rescueв коде Ruby - Используйте обработчик исключений
В следующих разделах показаны различные подходы к завершению выполнения Chef Infra Client.
Ключевое слово return
Ключевое слово return может использоваться для остановки обработки рецепта на основе условия, но продолжения обработки выполнения Chef Infra Client. Например:
file '/tmp/name_of_file' do
action :create
end
return if platform?('windows')
package 'name_of_package' do
action :install
end
где platform?('windows') — условие, заданное для ключевого слова return. При выполнении условия обработка рецепта прекращается. Этот подход полезен, когда нет необходимости продолжать обработку, например, когда пакет не может быть установлен. В такой ситуации вполне нормально, если обработка рецепта остановится.
Ключевое слово raise
В некоторых ситуациях может быть полезно полностью остановить выполнение Chef Infra Client, используя необработанное исключение. Ключевое слово raise может использоваться для остановки выполнения Chef Infra Client как в фазе компиляции, так и в фазе выполнения.
Примечание
fail, которое работает так же, но не рекомендуется и приведёт к предупреждениям Cookstyle. Используйте эти ключевые слова в рецепте (но вне любых блоков ресурсов) для вызова необработанного исключения во время фазы компиляции. Например:
file '/tmp/name_of_file' do
action :create
end
raise "message" if platform?('windows')
package 'name_of_package' do
action :install
end
где platform?('windows') — условие, которое вызовет необработанное исключение.
Используйте эти ключевые слова в ресурсе ruby_block для вызова необработанного исключения во время фазы выполнения. Например:
ruby_block "name" do
block do
# Ruby code with a condition, e.g. if ::File.exist?(::File.join(path, "/tmp"))
raise "message" # e.g. "Ordering issue with file path, expected foo"
end
end
Используйте эти ключевые слова в классе. Например:
class CustomError < StandardError; end
а затем позже:
def custom_error
raise CustomError, "error message"
end
или:
def custom_error
raise CustomError, "error message"
end
Блоки rescue
Поскольку рецепты написаны на Ruby, их можно написать так, чтобы обрабатывать условия ошибок с помощью блока rescue.
Например:
begin
dater = data_bag_item(:basket, 'flowers')
rescue Net::HTTPClientException
# maybe some retry code here?
raise 'message_to_be_raised'
end
где data_bag_item делает HTTP-запрос к Chef Infra Server для получения элемента набора данных с именем flowers. Если возникнет проблема, запрос вернёт Net::HTTPClientException. Блок rescue можно использовать для повторной попытки или иного обработки ситуации. Если блок rescue не сможет обработать ситуацию, то используется ключевое слово raise для указания сообщения об ошибке.
node.run_state
Используйте node.run_state для хранения временных данных во время выполнения Chef Infra Client. Эти данные могут передаваться между ресурсами и затем оцениваться в фазе выполнения. run_state — это пустой массив, который всегда удаляется в конце выполнения Chef Infra Client.
Например, следующий рецепт установит веб-сервер Apache, случайным образом выберет PHP или Perl в качестве языка сценариев и затем установит этот язык сценариев:
package 'httpd' do
action :install
end
ruby_block 'randomly_choose_language' do
block do
if Random.rand > 0.5
node.run_state['scripting_language'] = 'php'
else
node.run_state['scripting_language'] = 'perl'
end
end
end
package 'scripting_language' do
package_name lazy { node.run_state['scripting_language'] }
action :install
end
где:
- Ресурс ruby_block объявляет блок кода Ruby, который выполняется в фазе выполнения Chef Infra Client
- Оператор
ifслучайным образом выбирает PHP или Perl, сохраняя выбор вnode.run_state['scripting_language'] - Когда ресурс package должен установить пакет для языка сценариев, он ищет язык сценариев и использует тот, который определён в
node.run_state['scripting_language'] -
lazy {}гарантирует, что ресурс package оценивает это во время фазы выполнения Chef Infra Client (а не во время фазы компиляции)
При запуске этого рецепта Chef Infra Client выведет что-то вроде следующего:
* ruby_block[randomly_choose_language] action run
- execute the ruby block randomly_choose_language
* package[scripting_language] action install
- install version 5.3.3-27.el6_5 of package php
© Chef Software, Inc.
Licensed under the Creative Commons Attribution 3.0 Unported License.
The Chef™ Mark and Chef Logo are either registered trademarks/service marks or trademarks/servicemarks of Chef, in the United States and other countries and are used with Chef Inc's permission.
We are not affiliated with, endorsed or sponsored by Chef Inc.
https://docs.chef.io/recipes/