Назад | Перейти на главную страницу

Как правильно использовать рецепт Chef (соло)?

Может кто-нибудь объяснить мне, как работает шеф-повар? Это довольно широкий вопрос, поэтому, чтобы сузить его, у меня есть очень простой рецепт, который перебирает список пользователей и создает каждого из них, если они еще не существуют. Это не работает.

Из того, что я могу сказать, похоже, что цикл происходит, как я и ожидал. После завершения цикла мои команды bash для создания каждого пользователя выполняются один раз для каждой итерации в цикле. Однако при выполнении команд bash кажется, что они имеют значение пользователя только из первой итерации цикла.

Как правильно написать рецепт, который перебирает переменные данные, как в этом примере?

Вот рецепт:

node[:users].each do |user|
  puts "in loop for #{user['username']}"
  bash "create_user" do
    user "root"
    code do
      puts "running 'useradd' for #{user['username']}"
      "useradd #{user['username']}"
    end
    not_if do
      puts "checking /etc/passwd for #{user['username']}"
      "cat /etc/passwd | grep #{user['username']}"
    end
  end
end

Я тестирую это с помощью Vagrant со следующей настройкой:

Vagrant::Config.run do |config|
  config.vm.box = "precise32"
  config.vm.box_url = "http://files.vagrantup.com/precise32.box"
  config.vm.provision :chef_solo do |chef|
    chef.add_recipe "sample"
    chef.json = {
      :users => [
        {:username => 'testA'},
        {:username => 'testB'},
        {:username => 'testC'},
        {:username => 'testD'},
        {:username => 'testE'},
      ],
    }
  end
end

Сообщения, которые генерируются операторами put в рецепте, выглядят следующим образом:

2013-03-08T01:03:46+00:00] INFO: Start handlers complete.
in loop for testA

in loop for testB

in loop for testC

in loop for testD

in loop for testE

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Chef Run complete in 0.026071 seconds

Наблюдаемое вами поведение можно объяснить, понимая разницу между двумя ключевыми этапами выполнения клиента Chef: компиляцией и конвергенцией.

На этапе «компиляции» клиент Chef запускает код в ваших рецептах для создания сбор ресурсов. Это список Ресурсы которые вы сказали Chef управлять в вашей системе, вместе с их целевым состоянием. Например, ресурс Справочника, чтобы сказать, что /tmp/foo должен существовать и принадлежать root:

directory "/tmp/foo" do
  owner "root"
end

На этапе «конвергенции» клиент Chef использует провайдеры для загрузки текущего состояния каждого ресурса, а затем сравнивает его с целевым состоянием. Если они разные, Chef обновит систему. Для нашего ресурса Directory Chef создаст каталог, если он не существует, и при необходимости изменит его владельца на «root».

Ресурсы однозначно идентифицируются по их имени и типу - наш Справочник будет directory[/tmp/foo]. Когда у вас есть два ресурса с одинаковым именем, но разными атрибутами, могут произойти странные вещи - это объясняет вашу проблему, и ее можно исправить с помощью ответа Даррина Холста:

node[:users].each do |user|
  puts "in loop for #{user['username']}"
  bash "create_user_#{user}" do
    user "root"
    code do
      puts "running 'useradd' for #{user['username']}"
      "useradd #{user['username']}"
    end
    not_if do
      puts "checking /etc/passwd for #{user['username']}"
      "cat /etc/passwd | grep #{user['username']}"
    end
  end
end

Однако в этом конкретном случае вам будет полезно использовать ресурс Chef User. Вот замена вашего рецепта (без отладочных сообщений):

node[:users].each do |u|
  user u['username'] do
    action :create
  end
end

Почему это лучше, чем набор ресурсов bash?

  1. Ресурс User работает одинаково на разных платформах - один и тот же рецепт будет работать в операционных системах, которые для создания пользователей используют что-то другое, кроме useradd.
  2. Поставщики, ответственные за это, знают, как проверить, существует ли уже пользователь, поэтому вам не нужно not_if.
  3. Те же поставщики могут также использоваться для удаления пользователей, блокировки или разблокировки их паролей и обновления других атрибутов существующих учетных записей пользователей.

Но лучшая причина использовать правильные ресурсы - это то, что они более четко передают ваши намерения. Вероятно, ваша цель заключается не в том, чтобы запустить кучу команд оболочки, а в том, чтобы убедиться, что в вашей системе присутствуют некоторые пользователи. Команды, используемые для этого, являются лишь деталью реализации.

Провайдеры инкапсулируют эти детали, позволяя нам сосредоточиться на описании того, что мы хотим.

сделайте имя вашего скрипта уникальным ...

bash "create_user_#{user}" do

FWIW, я использовал https://github.com/fnichol/chef-user несколько раз, что позволяет создавать / удалять пользователей на основе атрибутов и тегов данных.