6

我有一个多节点 Web 服务的 Chef 配方,每个节点都需要获取其他节点的主机名和 IP,才能将其放入自己的本地配置中。

代码如下所示。问题是,当 node.set[][] 如图所示在 ruby​​_block 中进行分配时,当创建依赖它们的模板时,这些值是空的。如果我想创建那个模板,我必须把所有的 ruby​​_block 代码移到外面,并让它在配方中“松散”。这使得使用 Chefspec 等进行单元测试变得更加困难。

任何厨师大师都可以让我直截了当吗?像这样在 ruby​​_block 中做 node.set[] 是不可能的吗?如果是这样,为什么在文档中没有这样说?

$cm = { :name => "web", :hostname => "" , :ip_addr => "" }
$ca = { :name => "data", :hostname => "" , :ip_addr => "" }
$cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
$component_list = [$cm, $ca, $cg]

ruby_block "get host addresses" do
  block do
    for cmpnt in $component_list
       # do REST calls to external service to get cmpnt.hostname, ip_addr
       # .......
       node.set[cmpnt.name]['name'] = cmpnt.name
       node.set[cmpnt.name]['host'] = cmpnt.hostname
       node.set[cmpnt.name]['ip'] = cmpnt.ip_addr   
    end
  end
end

template "/etc/app/configuration/config.xml" do
  source "config.xml.erb"
  variables( :dataHost => node['data']['host'],
       :webHost =>  node['web']['host'],
       :gatewayHost =>  node['gateway']['host'] )
  action :create
end

我还添加了

  subscribes  :create, "ruby_block[get host addresses]", :immediately

到模板定义以确保 ruby​​_block 在模板创建之前运行。这并没有什么不同。

4

4 回答 4

5

我意识到这是一篇旧文章,但是为了将来参考,我刚刚遇到了这个要点,它给出了编译与收敛阶段中节点变量分配的一个很好的例子。要使要点适应您的示例,您需要将如下代码添加到您的ruby_block

       template_r = run_context.resource_collection.find(:template => "/etc/app/configuration/config.xml")

       template_r.content node['data']['host']
       template_r.content node['web']['host']
       template_r.content node['gateway']['host']

对于 Chef 11,另请参阅惰性属性评估

于 2014-03-14T22:13:59.980 回答
2

问题似乎是模板资源定义中的属性值在实际调用任何资源之前被评估。即文件首先作为简单的 Ruby 执行,编译资源,并且仅调用资源操作。到那个时候,已经太晚了。在尝试将某些属性操作封装到资源中时,我遇到了同样的问题。它根本行不通。如果有人知道这个问题的解决方案,我将不胜感激。

编辑:

b = ruby_block...
...
end
b.run_action(:create)

可能会成功。它立即调用资源。

于 2013-05-28T14:17:31.700 回答
1

对此最简单的答案是不使用 chef 属性并且不使用 ruby​​_block 来完成与 REST API 对话的工作。代码也可以移动到自定义资源以更好地重用:

unified_mode true
provides :my_resource

action :run do
  cm = { :name => "web", :hostname => "" , :ip_addr => "" }
  ca = { :name => "data", :hostname => "" , :ip_addr => "" }
  cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
  component_list = [cm, ca, cg]

  hash = {}

  for cmpnt in component_list
     # do REST calls to external service to get cmpnt.hostname, ip_addr
     # .......
     hash[cmpnt.name] = {}
     hash[cmpnt.name]['name'] = cmpnt.name
     hash[cmpnt.name]['host'] = cmpnt.hostname
     hash[cmpnt.name]['ip'] = cmpnt.ip_addr   
  end

  template "/etc/app/configuration/config.xml" do
    source "config.xml.erb"
    variables( :dataHost => hash['data']['host'],
       :webHost =>  hash['web']['host'],
       :gatewayHost =>  hash['gateway']['host'] )
    action :create
  end
end

通过使用统一模式并移动到自定义资源中,它还可以更轻松地使用节点属性,而无需使用惰性 {} 或 ruby​​_blocks。它还允许在调用此代码之前进行厨师配置(例如在执行 REST 调用之前设置 resolv.conf 或其他网络要求),而不必考虑在配方上下文中编译/收敛两个传递问题。

也没有理由使用像 ruby​​_block 这样的资源来进行不会改变管理系统的纯 ruby​​ 处理。在这种情况下,ruby_block 访问 REST 服务纯粹是为了收集数据。这不需要放入 Chef 资源中。从问题中不清楚是否这样做是因为发问者虽然这是“最佳实践”(在这种情况下不是),或者是否正在将执行移动到编译时间以允许其他不属于首先触发的问题的厨师资源(在这种情况下,使用自定义资源比使用 ruby​​_block 更好的解决方案)。

于 2020-08-20T23:13:24.487 回答
0

这个问题已经有一段时间了,但如果有人还在寻找它,懒惰的评估是你的朋友:

template '/tmp/sql_file.sql' do
  source "sql_file.sql.erb"
  mode 0700
  variables lazy {

    # Create a new instance of MySQL library
    mysql_lib = Acx::MySQL.new(
      '127.0.0.1', 'root', node['mysql']['service']['pass']
    )
    password = node['mysql']['service']['support_admin']['ct_password']

    # It returns the encrypted password after evaluate it, to 
    # be used in template variables
    { admin_password:  mysql_lib.encrypted_password(password) }
  }
end

https://docs.chef.io/resource_common.html#lazy-evaluation

于 2015-11-20T15:10:03.130 回答