Puppet Resource Type File e Múltiplos Sources
Há alguns dias um amigo (Elcimar Freitas) me disse que havia criado uma classe usando o modelo em trio, porém ele queria que certas máquinas recebessem arquivos personalizados, em um primeiro momento ele estava pensando em usar condicionais ou case para resolver o problema, o que é realmente possível, porém, não é muito prático, acabei dando uma dica que aprendi com o mago @dscobral - vulgo gandalf. Aqui neste post vamos resolver o problema dentro do recurso File, acompanhe a explicação e a solução.
Estudando o problema
Veja abaixo um exemplo típico de configuração em trio:
{% codeblock lang:puppet %}
class ntp {
package { 'ntp':
ensure => present
}
file { '/etc/ntp.conf':
ensure => present,
mode => 644,
owner => root,
group => root,
require => Package['ntp'],
notify => Service['ntp'],
source => "puppet:///files/ntp/ntp.conf"
}
service { 'ntp':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
require => File['/etc/ntp.conf'],
}
}
{% endcodeblock %}
Poderíamos utilizar tratamento condicional para enviar diferentes arquivos para diferentes nodes, veja um exemplo abaixo:
{% codeblock lang:puppet %} class ntp {
if $fqdn == 'servidor1.hacklab' {
$ntpfile = "ntp.conf.servidor1"
}
elsif $fqdn == "servidor2.hacklab" {
$ntpfile = "ntp.conf.servidor2"
}
else {
$ntpfile = "ntp.conf"
}
package { 'ntp':
ensure => present
}
file { '/etc/ntp.conf':
ensure => present,
mode => 644,
owner => root,
group => root,
require => Package['ntp'],
notify => Service['ntp'],
source => "puppet:///files/ntp/${ntpfile}"
}
service { 'ntp':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
require => File['/etc/ntp.conf'],
}
} {% endcodeblock %}
Ou então poderíamos utilizar o case para tratar a mesma questão de forma diferente, veja o exemplo abaixo:
{% codeblock lang:puppet %} class ntp {
case $fdqn {
servidor1.hacklab: { $ntpfile = "ntp.conf.servidor1" }
servidor2.hacklab: { $ntpfile = "ntp.conf.servidor2" }
default: { $ntpfile = "ntp.conf" }
}
package { 'ntp':
ensure => present
}
file { '/etc/ntp.conf':
ensure => present,
mode => 644,
owner => root,
group => root,
require => Package['ntp'],
notify => Service['ntp'],
source => "puppet:///files/ntp/${ntpfile}"
}
service { 'ntp':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
require => File['/etc/ntp.conf'],
}
} {% endcodeblock %}
Daria até para usar seletores, abaixo mais um exemplo:
{% codeblock lang:puppet %} class ntp {
package { 'ntp':
ensure => present
}
file { 'ntp.conf':
ensure => present,
mode => 644,
owner => root,
group => root,
require => Package['ntp'],
notify => Service['ntp'],
source => $fqdn ? {
'sevidor1.hacklab' => 'puppet:///files/ntp/ntp.conf.servidor1',
'servidor2.hacklab' => 'puppet:///files/ntp/ntp.conf.servidor1',
default => 'puppet:///files/ntp/ntp.conf',
},
}
service { 'ntp':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
require => File['ntp.conf'],
}
}
{% endcodeblock %}
Solucionando o problema
Porém nos três casos teríamos que escrever muitas linhas caso fosse necessário ter arquivos diferentes para vários servidores, seria repetitivo e cansativo, e nós utilizamos o puppet justamente para evitar o trabalho repetitivo.
Se lermos com cuidado o manual de resource types, ou se você tiver a sorte de ter um gandalf (@dcsobral) fazendo mentoria de puppet contigo, você poderá encontrar a solução dentro do resource type file, veja como fica a solução mais elegante - na minha opinião.
{% codeblock lang:puppet %} class ntp {
package { 'ntp':
ensure => present
}
file { 'ntp.conf':
ensure => present,
mode => 644,
owner => root,
group => root,
require => Package['ntp'],
notify => Service['ntp'],
source => [
"puppet:///files/ntp/ntp.conf.${fqdn}",
"puppet:///files/ntp/ntp.conf",
],
}
service { 'ntp':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
require => File['ntp.conf'],
}
}
{% endcodeblock %}
Observe que dentro de source eu declarei que se houver um arquivo chamado ntp.conf.nomedamaquina.dominio, ele vai usar esse arquivo, do contrário, se não existir algum arquivo com $fqdn no nome, ele passa para a próxima opção que seria o arquivo ntp.conf padrão.
Com este tipo de configuração podemos ter dezenas de arquivos de configuração no diretório /etc/puppet/files/ntp, cada um terminando com o nome da máquina a que se destina, isso seria entendido pelo source e processado na ordem determinada.
Eu chamo isso de múltiplos sources, veja mais um exemplo retirado do site da puppetlabs:
{% codeblock lang:puppet %}
file { “/etc/nfs.conf”: source => [ “puppet:///modules/nfs/conf.$host”, “puppet:///modules/nfs/conf.$operatingsystem”, “puppet:///modules/nfs/conf” ] } {% endcodeblock %}
No exemplo acima podem existir arquivos terminando com o nome da máquina, sistema operacional ou então a opção final é o arquivo padrão.
Dá para ver que existem muitas formas de se fazer a mesma coisa, o desafio é encontrar a mais eficiente para sua necessidade.
Eu normalmente uso CASE e SELETORES quando tenho que tratar nomes - diferentes - de pacotes em distros linux distintas, ou mesmo PATH para um arquivo de configuração que é diferente no Debian e no CENTOS, nestes casos são muito úteis de fato.
Referências
[s]
Guto