Puppet Coding. Uma introdução.

Olá a todos,

Chegamos ao terceiro e ultimo capitulo sobre puppet. A parte de como utilizar o puppet para configuração dos nossos servidores.
Neste campo, puppet é uma linguagem declarativa e é extremamente simples e de fácil leitura.

Puppet Node Graph Diagram and Code

Existem múltiplos IDE’s para trabalhar com puppet – pessoalmente recomendo visual studio code, com PDK e o plugin da linguagem pelo que recomendo estes.

 

pdk_new_module

 

Para primeiro exercício, vamos imaginar que os nossos dois clientes, são ligados ao nosso puppet server já existente (que instalamos no ultimo post) e que irá compilar e distribuir o nosso código:

O código é extremamente simples – KISS – e efetuará dois passos:

  • Configurar os DNS da nossa plataforma
  • Em servidores cujo nome contenha a palavra webserver, irá instalar o apache.
# Definir servidores de DNS a serem usados pela plataforma:
$dns_servers = ['172.16.3.161', '172.16.3.162']

# Definir uma expressão regular de maneira que seja passively efetuar match a hostnames que contenham a palavra "webserver"
$webserver_hostname_regex = /webserver/

# Execução de código.

# Configurar DNS servers para todos os nós:
node default {
class { 'dns':
servers => $dns_servers,
}
}

# Instalar o Apache se hostname conter "webserver"
node /$webserver_hostname_regex/ {
class { 'apache':
ensure => installed,
}
}

A explicação do código é simples:

  • A variável $dns_servers é definida para conter os endereços IP dos servidores DNS a serem usados.
  • A variável $webserver_hostname_regex é definida como uma expressão regular que corresponderá a nomes de host contendo a string “webserver”.

O bloco padrão do nó se aplica a todos os nós e configura os servidores DNS usando a classe dns com os servidores especificados.
O bloco node /$webserver_hostname_regex/ aplica-se apenas a nós cujo hostname corresponde à expressão regular e instala o Apache usando a classe apache com o parâmetro de
garantia definido como instalado.

Este código assume que as classes dns e apache foram definidas em outro lugar no nosso código Puppet ou num módulo. n.089/2022-2023
Podemos personalizar o código para responder ás nossas necessidades específicas, como especificar diferentes servidores DNS ou instalar software adicional com base em critérios diferentes.

E sobre as classes e módulos?

As configurações de classes são bastante fáceis de entender:

Para o nosso caso presente de DNS:

class dns (
Array[String] $servers,
) {

file { '/etc/resolv.conf':
content => template('dns/resolv.conf.erb'),
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
}
}

As leitura é efetuada da seguinte forma:

  • A classe dns usa um array de endereços IP do servidor DNS como entrada.
  • O recurso template é usado para gerir e criar o conteúdo do arquivo /etc/resolv.conf nos nós clientes.
  • O conteúdo do arquivo /etc/resolv.conf é especificado usando um modelo, que está localizado no diretório de modelos do módulo dns (supondo que você tenha um módulo dns com a estrutura de diretório apropriada).

Os parâmetros ensure, owner, group e mode são definidos para garantir que o arquivo esteja presente, seja propriedade do root e tenha as permissões apropriada.

O .erb em si, tem o seguinte formato:

# This file is managed by Puppet
# Do not edit manually

<% servers.each do |server| -%>
nameserver <%= server %>
<% end -%>

A explicação é igualmente muito simples:

O arquivo de template .erb contém um loop que corre sobre o array de endereços IP do servidor DNS (servidores) e gera uma linha de servidor de nomes para cada endereço IP.
O arquivo resolv.conf gerado conterá uma linha de servidor de nomes para cada endereço IP do servidor DNS especificado na lista/array de servidores de DNS.
Podemos personalizar a classe dns e o modelo resolv.conf.erb para atender às suas necessidades e ambiente específicos, podendo colocar dependente de expressões, localizações e objectivos de utilização.

E para o nosso caso na classe Apache?

O caso aqui é igualmente simples mas vai aumentando a complexidade.

class apache {
package { 'apache2':
ensure => installed,
}

service { 'apache2':
ensure => running,
enable => true,
require => Package['apache2'],
}

# Definir um fact para obter o hostname onde o nosso software apache irá ser executado.
$hostname = $::hostname

# Instalar apache se e so se o nossos hostname conter a palavra "webserver"
if $hostname =~ /webserver/ {
file { '/var/www/html/index.html':
content => 'Bem vindo ao nosso servidor Web Apache!',
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
require => Package['apache2'],
}
}
}

A explicação aqui é igualmente super fácil de entender:

  • A classe apache instala o pacote apache2, inicia e ativa o serviço apache2 e, opcionalmente, cria um arquivo de índice no diretório raiz da web (/var/www/html) se o nome do host corresponder ao padrão regex definido por nós
  • A instrução if usa uma expressão regular para verificar se o nome do host contém a string “webserver”.
    Se o nome do host corresponder ao padrão, o recurso de arquivo criará um arquivo de índice com o conteúdo e as permissões especificados.
    Observem que, neste exemplo, definimos um fato personalizado $hostname para obter o nome do host do nó cliente.
    Este fato (e tantos outros – e até podemos escrever código em bash que obtenha factos para nós) está automaticamente disponível no puppet e pode ser usado no nosso código. Como alternativa, você pode usar o fato do nome do host diretamente (ou seja, $::hostname) sem definir um fato personalizado.

Isto ainda por ser alterado de forma a personalizar a classe apache para atender às nossas necessidades específicas, como instalar software adicional, configurar hosts virtuais ou usar um padrão regex diferente para corresponder aos nomes dos hosts.

E se eu escrever código mas não o quiser estar sempre a alterar sempre que quiser definir sempre os mesmos DNS? Ou os pacotes de apache?
Posso ter os arrays definidos em ficheiros hierárquicos de configuração? Claro que sim! Chama-se Hiera:

Usando o nosso exemplo acima, podemos definir assim:

# Hiera file para producao
---
dns_servers:
- 8.8.8.8
- 8.8.4.4

webserver_hostname_regex: 'webserver'

E o nosso código ficará algo como:

# Obter o codigo diretamente do hiera
$dns_servers = hiera('dns_servers')

# obter regex diretamente do hiera
$webserver_hostname_regex = hiera('webserver_hostname_regex')

# Configurar DNS
node default {
class { 'dns':
servers => $dns_servers,
}
}

# Instalar apache se o hostname contiver "webserver" no nome
node /$webserver_hostname_regex/ {
class { 'apache':
ensure => installed,
}
}

Podemos notar que agora, para o exemplo futuro, não precisaremos de alterar o nosso codigo.pp mas apenas necessitaremos de alterar os valores dentro do Hiera para que seja carregado e executado dentro do nosso codigo, como se dele fizesse parte integrante.

E o que é em si o Hiera? Falo muito deste nome mas não o expliquei ainda:

No puppet, o Hiera é uma ferramenta de pesquisa de valor-chave que nos permite externalizar os nossos dados de configuração de código.
Os dados Hiera podem ser armazenados em diferentes formatos de arquivo, como YAML, JSON ou EYAML, e podem ser organizados em uma hierarquia de fontes de dados.

Para configurar o puppet para usar os dados contidos no Hiera, precisamos especificar o local do arquivo de configuração do Hiera (hiera.yaml) e o local dos arquivos de dados do Hiera no arquivo puppet.conf (o mesmo que definimos quando instalamos o servidor). Também podemos especificar essas configurações na linha de comando ou nas variáveis ​​de ambiente.

Por norma, o puppet procura por arquivos de dados Hiera no diretório de dados de cada módulo, na seguinte ordem:

  1. data/common.yaml
  2. dados/<ambiente>.yaml
  3. dados/<módulo>.yaml
  4.  Onde <environment> é o nome do ambiente puppet e <module> é o nome do módulo.

Por exemplo, se tivermos um módulo chamado dns e um ambiente chamado produção, o puppet procurará os seguintes arquivos de dados Hiera:

  1. dns/data/common.yaml
  2. dns/data/produção.yaml
  3. dns/data/dns.yaml

Nota: é importante colocar os  arquivos de dados Hiera em qualquer local acessível pelo agente Puppet nos nós clientes, como um compartilhamento de arquivo ou um servidor web. Garantam que definem as configurações apropriadas no vosso ficheiro puppet.conf ou na linha de comando para especificar a localização dos mesmos.

E assim acabamos este grupo de posts sobre automação utilizando o puppet. Espero que vos tenha despertado a atenção sobre este produto, e da sua utilidade na nossa profissão ou hobby.
Pode não parecer muito útil ou pratico em ambientes pequenos, agora imaginem que escalam e teem necessidade de fazer alteração em 200, 300, 500, 1000 servidores de uma vez.
Nestes casos, puppet irá sem sobra de duvida, vos salvar a vida.

Até ao próxima semana.
Se tiverem duvidas, já sabem onde me podem encontrar.

Um abraço,
Nuno