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

Puppet: Как установить порядок строк в файле?

Серверы Linux в моей компании управляются Puppet.

Есть модуль DNS, который настраивает /etc/resolv.conf на всех серверах в зависимости от физического местоположения, которое настроено как facter стоимость.

Как вы знаете /etc/resolv.conf файл выглядит так:

search domain.local
nameserver 1.1.1.1
nameserver 2.2.2.2

Все имена хостов серверов в компании заканчиваются двумя цифрами, например:

proxy73

Чтобы разделить сетевой трафик DNS между двумя DNS-серверами, я написал новый марионеточный модуль, который вырезает последние две цифры имени хоста, и если это нечетное число, то /etc/resolv.conf файл должен выглядеть, как показано выше, но если цифры образуют нечетное число, то /etc/resolv.conf файл должен выглядеть так:

search domain.local
nameserver 2.2.2.2
nameserver 1.1.1.1

Но моя проблема в том, что независимо от того, как я пишу манифест, строки всегда упорядочиваются как первый сервер, а затем второй вместо второго сервера, а затем первый сервер.

Соответствующая часть написанного мною манифеста выглядит так (см. Часть под if $::oddip == false потому что это часть, которая не работает):

class dns_new::config {
  case $::dcd {
 'ny4': {
      if $::oddip == 'true' {
        file_line { "ny4 search domain":
          ensure => present,
          line   => "${::dns_new::params::searchdomny4}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns1 first":
          ensure => present,
          line   => "${::dns_new::params::ny4dns1}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns2 second":
          ensure => present,
          line   => "${::dns_new::params::ny4dns2}",
          path   => "/etc/resolv.conf",
          }
      }
      elsif $::oddip == 'false'  {
        file_line { "ny4 search domain":
          ensure => present,
          line   => "${::dns_new::params::searchdomny4}",
          path   => "/etc/resolv.conf",
          }
        file_line { "ny4dns2 first":
          ensure => present,
          line   => "${::dns_new::params::ny4dns2}",
          path   => "/etc/resolv.conf",
          require => File_line["ny4 search domain"],
          before => File_line["ny4dns1 second"],
          }
        file_line { "ny4dns1 second":
          ensure => present,
          line   => "${::dns_new::params::ny4dns1}",
          path   => "/etc/resolv.conf",
          require => File_line["ny4dns2 first"],
          }
      }
    }

Как видите, я пытался установить порядок с помощью before директива.

Это все, что касается настройки нового сервера, но как я могу установить порядок строк для уже установленного сервера?

Редактировать # 2:

sysadmin1183, я добавил "делать", как вы показали, теперь ошибка такая:

[root@nyproxy33 ~]# puppet agent -t
Info: Retrieving plugin
Info: Loading facts
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: compile error
/etc/puppet/environments/production/modules/dns_new/templates/resolv.conf.erb:27: syntax error, unexpected $end, expecting kEND
; _erbout
         ^
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run

27-я строка:

<% end %>

Пытался отредактировать на:

<% end -%>

Но получаю тот же результат ...

Редактировать # 3: config.pp выглядит так:

class dns_new::config {
  file { "/etc/resolv.conf":
      path    => '/etc/resolv.conf',
      ensure  => present,
      owner   => "root",
      group   => "root",
      mode    => "775",
      content => template("dns_new/resolv.conf.erb"),
      }

  case $::dcd {
    'ny4': {
      $search_dom = $::dns_new::params::searchdomny4
      if $::oddip == 'true' {
        $dns_list = [ "${::dns_new::params::ny4dns1}", "${::dns_new::params::ny4dns2}" ]
        }
      elsif $::oddip == 'false' {
        $dns_list = [ "${::dns_new::params::ny4dns2}", "${::dns_new::params::ny4dns1}" ]
        }
    }

В resolv.conf.erb файл выглядит так:

search <%= @search_dom %>
<% dns_list.each do |serv| -%>
nameserver <%= serv %>
<% end -%>

Запуск марионетки:

[root@nyproxy33 ~]# puppet agent -t
Info: Retrieving plugin
Info: Loading facts
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Failed to parse template dns_new/resolv.conf.erb:
  Filepath: /usr/lib/ruby/site_ruby/1.8/puppet/parser/templatewrapper.rb
  Line: 81
  Detail: Could not find value for 'dns_list' at /etc/puppet/environments/production/modules/dns_new/templates/resolv.conf.erb:2
 at /etc/puppet/environments/production/modules/dns_new/manifests/config.pp:8 on node nyproxy33.ny4.peer39.com
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run

В этом случае вам может быть лучше подойдет шаблон. Что-то вроде...

# This is done to bring the variable into scope.
$search_dom = $::dns_new::params::searchdomny4
if $::oddip == 'true' {
  $dns_order = [ '1.1.1.1', '2.2.2.2' ]
} elsif $::oddip == 'false' {
  $dns_order = [ '2.2.2.2', '1.1.1.1' ]
}

file { '/etc/resolv.conf':
  [usual stuff]
  content => template('dns_new/resolv.conf.erb'),
}

С шаблоном ERB, который выглядит примерно так:

new_dns/templates/resolv.conf.erb:

search <%= @search_dom %>
<% @dns_order.each do |serv| %>
nameserver <%= serv %>
<% end -%>

Что должно создать файл resolv.conf в том порядке, в котором вы хотите.

Другой вариант - просто закодировать список DNS-серверов в модуле и использовать рубиновый код в шаблоне ERB, чтобы определить, проходите ли вы по массиву (каждый) или поднимаетесь по нему (reverse.each). Это будет выглядеть так:

$search_dom = $::dns_new::params::searchdomny4
$dns_list   = $::dns_new::params::dnslist
$oddip      = $::oddip
file { '/etc/resolv.conf':
  [usual stuff]
  content => template('dns_new/resolv.conf.erb')
}

С более сложным ERB, отформатированным как:

search <%= @search_dom %>
<% if @odd_ip == 'true' %>
  <% @dns_list.each do |srv| -%>
nameserver <%= srv %>
  <% end -%>
<% elsif @odd_ip == 'false' -%>
  <% @dns_list.reverse.each do |srv| -%>
nameserver <%= srv %>
  <% end -%>
<% end -%>

Если вы раньше не делали шаблоны, ключ к разметке примерно такой:

<%   : Here is ruby code.
<%=  : Here is an evaluated value. Replace this block with the 
         result, or enter a blank line.
-%>  : End a block. Don't render a blank line if it doesn't evaluate to anything.

Шаблон будет оцениваться построчно. Первая строка - это простая вещь, отбрасывающая «серверную» часть файла resolv conf. Во второй строке все становится сложнее: мы вызываем функции Ruby. Это то, что позволяет нам сбрасывать как можно больше nameserver строк, как есть в массиве.


Я вижу, что вы пытаетесь сделать в Еврорадио, но я думаю, что вы слишком усложняете это. Вы кодируете логику локализации (nj vs ams vs lax) в самом ERB. Это можно сделать, но, возможно, вам больше повезет, если вы выполните эту часть кода марионетки. Более вероятно, что кто-то еще сможет его прочитать, если эта часть логики совпадает с кодом марионетки.

dns_new/manifests/config.pp:

case $::dcd {
  'ny4': {
            $search_domain = $::dns_new::params::searchdomny4
            $dns_list = [ "${::dns_new::params::ny4dns1}",    "${::dns_new::params::ny4dns2}" ]
         }
  'nj':  {
            $search_domain = $::dns_new::params::searchdomnj
            $dns_list = [ "${::dns_new::params::njdns1}",    "${::dns_new::params::njdns2}" ]
         }
  'ams2': {
            $search_domain = $::dns_new::params::searchdomams2
            $dns_list = [ "${::dns_new::params::ams2dns1}",    "${::dns_new::params::ams2dns2}" ]
          }
}

file { '/etc/resolv.conf':
  [the usual stuff]
  content = template('dns_new/resolv.conf.erb')
}

Теперь у вас есть две переменные в ERB. search_domain и dns_list. Это немного укорачивает ERB:

dns_new/templates/resolv.conf.erb:

search <%= @search_domain %>
<% dns_list.each do |serv| -%>
nameserver <%= serv %>
<% end -%>

Если вам интересно, почему я назначаю переменные в классе и использую их в ERB вместо использования переменных в классе params, это из-за неинтуитивного способа работы переменных вне области видимости в файлах ERB. Намного проще и удобнее назначать переменные, которые будут использоваться в файле ERB в том же классе, который вызывает шаблон.

Метапараметр «до» действительно говорит только о порядке выполнения ресурсов, а не о порядке строк в файле.

На вашем месте я бы стремился управлять resolv.conf с помощью конструктов марионеток первого класса: отдельного модуля, который управляет им как файловым ресурсом (их, вероятно, несколько в кузнице марионеток), или написать свой собственный небольшой шаблон, который явно упорядочивает указанные параметры сервера имен.

Другой вариант - указать оба сервера имен в одном ресурсе file_line, используя \n разделить их:

file_line { "ny4dns2 first":
  ensure => present,
  line   => "${::dns_new::params::ny4dns1}\n${::dns_new::params::ny4dns2}",
  path   => "/etc/resolv.conf",
  require => File_line["ny4 search domain"],
  after =>  "${::dns_new::params::searchdomny4}"
 }

Это не изменит их порядок, если они находятся в неправильном порядке, но по крайней мере добавит их в правильном порядке прямо под поисковым запросом (возможно, перечисляя 4 сервера имен, что больше 3 (MAXNS в resolv.h), но с использованием только ресурсов file_line избежать этого может быть сложно или невозможно).

Так же after Параметр относится к ресурсам file_line, подсказывая, куда вставить строку, и before - общий параметр ресурса, говорящий об упорядочивании ресурсов.