One place for hosting & domains

      Escrevendo

      Gerenciamento de Configuração 101: Escrevendo Receitas do Chef


      Introdução

      Em poucas palavras, o gerenciamento de configuração de servidor (também conhecido como IT Automation) é uma solução para transformar a sua administração de infraestrutura em uma base de código, descrevendo todos os processos necessários para fazer o deploy de um servidor em um conjunto de scripts de provisionamento que podem ser versionados e facilmente reutilizados. Isso pode melhorar muito a integridade de qualquer infraestrutura de servidor ao longo do tempo.

      Em um guia anterior, falamos sobre os principais benefícios da implementação de uma estratégia de gerenciamento de configuração para a infraestrutura de servidor, como as ferramentas de gerenciamento de configuração funcionam e o que essas ferramentas têm em comum normalmente.

      Esta parte da série o guiará pelo processo de automatização do provisionamento de servidores usando o Chef, uma poderosa ferramenta de gerenciamento de configuração que aproveita a linguagem de programação Ruby para automatizar a administração e o provisionamento da infraestrutura. Vamos nos concentrar na terminologia da linguagem, sintaxe e recursos necessários para criar um exemplo simplificado para automatizar completamente o deployment de um servidor web Ubuntu 18.04 usando o Apache.

      Esta é a lista de passos que precisamos automatizar para alcançar nosso objetivo:

      1. Atualizar o cache do apt
      2. Instalar o Apache
      3. Criar um diretório raiz de documentos personalizado (document root)
      4. Colocar um arquivo index.html no document root personalizado
      5. Aplicar um template para configurar nosso virtual host personalizado
      6. Reiniciar o Apache

      Começaremos examinando a terminologia usada pelo Chef, seguida de uma visão geral dos principais recursos da linguagem que podem ser usados para escrever recipes ou receitas. No final deste guia, compartilharemos o exemplo completo para que você possa experimentar sozinho.

      Nota: este guia pretende apresentar a linguagem do Chef e como escrever receitas para automatizar o provisionamento de servidor. Para uma visão mais introdutória do Chef, incluindo as etapas necessárias para instalar e começar com esta ferramenta, consulte a documentação oficial do Chef.

      Começando

      Antes de podermos avançar para uma visão mais prática do Chef, é importante nos familiarizarmos com importantes terminologias e conceitos introduzidos por esta ferramenta.

      Termos do Chef

      • Chef Server: um servidor central que armazena informações e gerencia o provisionamento dos nodes
      • Chef Node: um servidor individual que é gerenciado por um Chef Server
      • Chef Workstation: uma máquina controladora na qual os provisionamentos são criados e carregados para o Chef Server
      • Recipe (receita): um arquivo que contém um conjunto de instruções (recursos) a serem executados. Uma receita deve estar contida dentro de um Cookbook
      • Resource (recurso): uma parte do código que declara um elemento do sistema e que ação deve ser executada. Por exemplo, para instalar um pacote, declaramos um recurso package com a ação install
      • Cookbook (livro de receitas): uma coleção de receitas e outros arquivos relacionados organizados de forma predefinida para facilitar o compartilhamento e a reutilização de partes de um provisionamento
      • Attributes (atributos): detalhes sobre um node específico. Os Attributes (atributos) podem ser automáticos (veja a próxima definição) e também podem ser definidos nas receitas
      • Automatic Attributes: variáveis globais que contêm informações sobre o sistema, como interfaces de rede e sistema operacional (conhecidos como facts em outras ferramentas). Esses atributos automáticos são coletados por uma ferramenta chamada Ohai
      • Services: usado para acionar alterações de status do serviço, como reiniciar ou parar um serviço

      Formato da Receita

      As receitas do Chef são escritas usando Ruby. Uma receita é basicamente uma coleção de definições de recursos que criarão um conjunto passo a passo de instruções a serem executadas pelos nodes. Essas definições de recursos podem ser combinadas com o código Ruby para maior flexibilidade e modularidade.

      Abaixo você pode encontrar um exemplo simples de uma receita que irá executar o apt-get update e instalar o vim posteriormente:

      execute "apt-get update" do
       command "apt-get update"
      end
      
      apt_package "vim" do
       action :install
      end
      
      

      Escrevendo Receitas

      Trabalhando com Variáveis

      As variáveis locais podem ser definidas nas receitas como variáveis locais regulares do Ruby. O exemplo abaixo mostra como criar uma variável local que é usada posteriormente dentro de uma definição de recurso:

      package  = "vim"
      
      apt_package package do
       action :install
      end
      

      Essas variáveis, no entanto, têm um escopo limitado, sendo válidas apenas dentro do arquivo em que foram definidas. Se você deseja criar uma variável e disponibilizá-la globalmente, para poder usá-la em qualquer um dos seus cookbooks ou receitas, você precisa definir um atributo personalizado.

      Usando Atributos

      Os atributos representam detalhes sobre um node. O Chef possui atributos automáticos, que são os atributos coletados por uma ferramenta chamada Ohai e contêm informações sobre o sistema (como plataforma, nome do host e endereço IP padrão), mas também permitem definir seus próprios atributos personalizados.

      Os atributos têm níveis de precedência diferentes, definidos pelo tipo de atributo que você cria. Os atributos default são a escolha mais comum, pois ainda podem ser sobrescritos por outros tipos de atributos quando desejado.

      O exemplo a seguir mostra como o exemplo anterior seria com um atributo de node default em vez de uma variável local:

      node.default['main']['package'] = "vim"
      
      apt_package node['main']['package'] do
       action :install
      end
      

      Há dois detalhes a serem observados neste exemplo:

      A prática recomendada ao definir variáveis de node é organizá-las como hashes usando o cookbook atual em uso como chave. Nesse caso, usamos main, porque temos um cookbook com o mesmo nome. Isso evita confusão se você estiver trabalhando com vários cookbooks que podem ter atributos com nomes semelhantes.
      Observe que usamos node.default ao definir o atributo, mas ao acessar seu valor posteriormente, usamos node diretamente. O uso do node.default define que estamos criando um atributo do tipo default. Esse atributo pode ter seu valor sobrescrito por outro tipo com precedência mais alta, como atributos normal ou override.

      A precedência dos atributos pode ser um pouco confusa no início, mas você se acostumará com isso depois de alguma prática. Para ilustrar o comportamento, considere o seguinte exemplo:

      node.normal['main']['package']  = "vim"
      
      node.override['main']['package'] = "git"
      
      node.default['main']['package'] = "curl"
      
      apt_package node['main']['package'] do
       action :install
      end
      

      Você sabe qual pacote será instalado neste caso? Se você achou que é o git, adivinhou corretamente. Independentemente da ordem em que os atributos foram definidos, a maior precedência do tipo override fará com que o node['main']['package'] seja avaliado como git.

      Usando Loops

      Os loops são normalmente usados para repetir uma tarefa usando diferentes valores de entrada. Por exemplo, em vez de criar 10 tarefas para instalar 10 pacotes diferentes, você pode criar uma única tarefa e usar um loop para repetir a tarefa com todos os pacotes diferentes que deseja instalar.

      O Chef suporta todas as estruturas de loop Ruby para criar loops dentro de receitas. Para uso simples, each é uma escolha comum:

      ['vim', 'git', 'curl'].each do |package|
       apt_package package do
         action :install
       end
      end
      

      Em vez de usar um array inline, você também pode criar uma variável ou atributo para definir os parâmetros que deseja usar dentro do loop. Isso manterá as coisas mais organizadas e fáceis de ler. Abaixo, o mesmo exemplo agora usando uma variável local para definir os pacotes que devem ser instalados:

      packages = ['vim', 'git', 'curl']
      
      packages.each do |package|
       apt_package package do
         action :install
       end
      end
      

      Usando Condicionais

      Condicionais podem ser usadas para decidir dinamicamente se um bloco de código deve ou não ser executado, com base em uma variável ou em uma saída de um comando, por exemplo.

      O Chef suporta todos as condicionais do Ruby para criar instruções condicionais nas receitas. Além disso, todos os tipos de recursos suportam duas propriedades especiais que avaliarão uma expressão antes de decidir se a tarefa deve ser executada ou não: if_only e not_if.

      O exemplo abaixo verificará a existência do php antes de tentar instalar a extensão php-pear. Ele usará o comando which para verificar se existe um executável php atualmente instalado neste sistema. Se o comando which php retornar falso, esta tarefa não será executada:

      apt_package "php-pear" do
       action :install
       only_if "which php"
      end
      

      Se quisermos fazer o oposto, executando um comando o tempo todo exceto quando uma condição é avaliada como verdadeira, usamos not_if. Este exemplo instalará o php5, a menos que o sistema seja o CentOS:

      apt_package "php5" do
       action :install
       not_if { node['platform'] == 'centos' }
      end
      

      Para realizar avaliações mais complexas, ou se você desejar executar várias tarefas sob uma condição específica, você pode usar qualquer uma das condicionais padrão do Ruby. O exemplo a seguir só executará apt-get update quando o sistema for Debian ou Ubuntu:

      if node['platform'] == 'debian' || node['platform'] == 'ubuntu'
       execute "apt-get update" do
         command "apt-get update"
       end
      end
      

      O atributo node['platform'] é um atributo automático do Chef. O último exemplo foi apenas para demonstrar uma construção condicional mais complexa, no entanto, pode ser substituída por um teste simples usando o atributo automático node['platform_family'], que retornaria “debian” para os sistemas Debian e Ubuntu.

      Trabalhando com Templates

      Os templates geralmente são usados para definir arquivos de configuração, permitindo o uso de variáveis e outros recursos destinados a tornar esses arquivos mais versáteis e reutilizáveis.

      O Chef usa templates Embedded Ruby (ERB), que é o mesmo formato usado pelo Puppet. Eles suportam condicionais, loops e outros recursos do Ruby.

      Abaixo está um exemplo de um modelo de ERB para configurar um virtual host no Apache, usando uma variável para definir o document root para esse host:

      <VirtualHost *:80>
          ServerAdmin webmaster@localhost
          DocumentRoot <%= @doc_root %>
      
          <Directory <%= @doc_root %>>
              AllowOverride All
              Require all granted
          </Directory>
      </VirtualHost>
      

      Para aplicar o template, precisamos criar um recurso template. É assim que você aplicaria esse template para substituir o virtual host padrão do Apache:

      template "/etc/apache2/sites-available/000-default.conf" do
       source "vhost.erb"
       variables({ :doc_root => node['main']['doc_root'] })
       action :create
      end 
      

      O Chef faz algumas suposições ao lidar com arquivos locais, a fim de reforçar a organização e a modularidade. Nesse caso, o Chef procuraria um arquivo de template vhost.erb dentro de uma pasta templates que deveria estar no mesmo cookbook em que esta receita está localizada.

      Ao contrário das outras ferramentas de gerenciamento de configuração que vimos até agora, o Chef tem um escopo mais estrito para variáveis. Isso significa que você precisará fornecer explicitamente todas as variáveis que planeja usar dentro de um template, ao definir o recurso template. Neste exemplo, usamos o método variables para passar adiante o atributo doc_root que precisamos no modelo de virtual host.

      Definindo e Acionando Serviços

      Os recursos de serviço são usados para garantir que os serviços sejam inicializados e ativados. Eles também são usados para acionar a reinicialização do serviço.

      No Chef, os recursos de serviço precisam ser declarados antes de você tentar notificá-los, caso contrário, você receberá um erro.

      Vamos levar em consideração o nosso exemplo anterior de uso de templates, onde configuramos um virtual host no Apache. Se você deseja garantir que o Apache seja reiniciado após uma mudança no virtual host, primeiro crie um recurso service para o serviço Apache. É assim que esse recurso é definido no Chef:

      service "apache2" do
        action [ :enable, :start ]
      end
      

      Agora, ao definir o recurso template, você precisa incluir uma opção notify para acionar uma reinicialização:

      template "/etc/apache2/sites-available/000-default.conf" do
       source "vhost.erb"
       variables({ :doc_root => node['main']['doc_root'] })
       action :create
       notifies :restart, resources(:service => "apache2")
      end
      

      Exemplo de Receita

      Agora, vamos dar uma olhada em uma receita que automatizará a instalação de um servidor Web Apache em um sistema Ubuntu 14.04, conforme discutido na introdução deste guia.

      O exemplo completo, incluindo o arquivo de template para configurar o Apache e um arquivo HTML para ser servido pelo servidor web, pode ser encontrado no Github. A pasta também contém um arquivo Vagrant que permite testar a receita em uma configuração simplificada, usando uma máquina virtual gerenciada pelo Vagrant.

      Abaixo você pode encontrar a receita completa:

      • node.default['main']['doc_root'] = "/vagrant/web"
      • execute "apt-get update" do
      • command "apt-get update"
      • end
      • apt_package "apache2" do
      • action :install
      • end
      • service "apache2" do
      • action [ :enable, :start ]
      • end
      • directory node['main']['doc_root'] do
      • owner 'www-data'
      • group 'www-data'
      • mode '0644'
      • action :create
      • end
      • cookbook_file "#{node['main']['doc_root']}/index.html" do
      • source 'index.html'
      • owner 'www-data'
      • group 'www-data'
      • action :create
      • end
      • template "/etc/apache2/sites-available/000-default.conf" do
      • source "vhost.erb"
      • variables({ :doc_root => node['main']['doc_root'] })
      • action :create
      • notifies :restart, resources(:service => "apache2")
      • end

      Receita Explicada

      linha 1

      A receita começa com uma definição de atributo, node['main']['doc_root']. Poderíamos ter usado uma variável local simples aqui, no entanto, na maioria dos cenários de casos de uso, as receitas precisam definir variáveis globais que serão usadas nas receitas incluídas ou em outros arquivos. Para essas situações, é necessário criar um atributo em vez de uma variável local, pois a última possui um escopo limitado.

      linhas 3-5

      Esse recurso execute executa um apt-get update.

      linhas 7-10

      Este recurso apt_package instala o pacote apache2.

      linhas 12-15

      Este recurso service ativa e inicia o serviço apache2. Posteriormente, precisaremos notificar esse recurso para reiniciar o serviço. É importante que a definição de serviço venha antes de qualquer recurso que tente notificar um serviço, caso contrário, você receberá um erro.

      linhas 17-22

      Este recurso directory usa o valor definido pelo atributo personalizado node['main']['doc_root'] para criar um diretório que servirá como nosso document root

      linhas 24-29

      Um recurso cookbook_file é usado para copiar um arquivo local em um servidor remoto. Este recurso copiará nosso arquivo index.html e o colocará dentro do document root que criamos em uma tarefa anterior.

      linhas 31-36

      Por fim, esse recurso template aplica nosso template de virtual host do Apache e notifica o serviço apache2 para uma reinicialização.

      Conclusão

      O Chef é uma poderosa ferramenta de gerenciamento de configuração que utiliza a linguagem Ruby para automatizar o provisionamento e o deployment de servidores. Ele lhe oferece liberdade para usar os recursos de linguagem padrão para obter o máximo de flexibilidade, além de oferecer DSLs personalizadas para alguns recursos.



      Source link

      Gerenciamento de Configuração 101: Escrevendo Manifests do Puppet


      Introdução

      Em poucas palavras, o gerenciamento de configuração de servidor (também conhecido como IT Automation) é uma solução para transformar a sua administração de infraestrutura em uma base de código, descrevendo todos os processos necessários para fazer o deploy de um servidor em um conjunto de scripts de provisionamento que podem ser versionados e facilmente reutilizados. Isso pode melhorar muito a integridade de qualquer infraestrutura de servidor ao longo do tempo.

      Em um guia anterior, falamos sobre os principais benefícios da implementação de uma estratégia de gerenciamento de configuração para a infraestrutura de servidor, como as ferramentas de gerenciamento de configuração funcionam e o que essas ferramentas têm em comum normalmente.

      Esta parte da série o guiará pelo processo de automatização do provisionamento de servidores usando o Puppet, uma ferramenta popular de gerenciamento de configuração capaz de gerenciar infraestruturas complexas de maneira transparente, usando um servidor mestre ou master para orquestrar a configuração dos nodes. Vamos nos concentrar na terminologia da linguagem, sintaxe e recursos necessários para criar um exemplo simplificado para automatizar completamente o deployment de um servidor web Ubuntu 18.04 usando o Apache.

      Esta é a lista de passos que precisamos automatizar para alcançar nosso objetivo:

      1. Atualizar o cache do apt
      2. Instalar o Apache
      3. Criar um diretório raiz de documentos personalizado (document root)
      4. Colocar um arquivo index.html no document root personalizado
      5. Aplicar um modelo para configurar nosso virtual host personalizado
      6. Reiniciar o Apache

      Começaremos examinando a terminologia usada pelo Puppet, seguida de uma visão geral dos principais recursos da linguagem que podem ser usados para escrever manifests. No final deste guia, compartilharemos o exemplo completo para que você possa experimentar sozinho.

      Nota: este guia pretende apresentar a linguagem do Puppet e como escrever manifests para automatizar o provisionamento de servidor. Para uma visão mais introdutória do Puppet, incluindo as etapas necessárias para instalar e começar com esta ferramenta, consulte a documentação oficial do Puppet.

      Começando

      Antes de podermos avançar para uma visão mais prática do Puppet, é importante nos familiarizarmos com importantes terminologias e conceitos introduzidos por esta ferramenta.

      Termos do Puppet

      • Puppet Master: o servidor principal que controla a configuração nos nodes
      • Puppet Agent Node: um node controlado por um Puppet Master
      • Manifest: um arquivo que contém um conjunto de instruções a serem executadas
      • Resource (recurso): uma parte do código que declara um elemento do sistema e como seu estado deve ser alterado. Por exemplo, para instalar um pacote, precisamos definir um recurso package e garantir que seu estado esteja definido como installed
      • Module: uma coleção de manifests e outros arquivos relacionados organizados de uma forma predefinida para facilitar o compartilhamento e a reutilização de partes de um provisionamento
      • Class: Assim como nas linguagens de programação regulares, as classes são usadas no Puppet para organizar melhor o provisionamento e facilitar a reutilização de partes do código
      • Facts: variáveis globais que contêm informações sobre o sistema, como interfaces de rede e sistema operacional
      • Services: usado para disparar alterações de status do serviço, como reiniciar ou parar um serviço

      O provisionamento no Puppet é escrito usando uma DSL (linguagem específica de domínio) personalizada baseada em Ruby.

      Recursos

      Com o Puppet, as tarefas ou etapas são definidas declarando recursos. Os recursos podem representar pacotes, arquivos, serviços, usuários e comandos. Eles podem ter um estado, que acionará uma alteração no sistema caso o estado de um recurso declarado seja diferente do que está atualmente no sistema. Por exemplo, um recurso package definido como installed no seu manifest acionará uma instalação do pacote no sistema se o pacote não tiver sido instalado anteriormente.

      É assim que um recurso package se parece:

      package { 'nginx':
          ensure  => 'installed'
      }
      

      Você pode executar qualquer comando arbitrário declarando um recurso exec, como o seguinte:

      exec { 'apt-get update':
          command => '/usr/bin/apt-get update'
      }
      

      Observe que a parte apt-get update na primeira linha não é a declaração de comando real, mas um identificador para este recurso exclusivo. Frequentemente, precisamos fazer referência a outros recursos de dentro de um recurso e usamos o identificador deles para isso. Nesse caso, o identificador é apt-get update, mas poderia ser qualquer outra string.

      Dependência de Recurso

      Ao escrever manifests, é importante ter em mente que o Puppet não avalia os recursos na mesma ordem em que eles são definidos. Essa é uma fonte comum de confusão para quem está começando com o Puppet. Os recursos devem definir explicitamente a dependência entre si, caso contrário, não há garantia de qual recursos será avaliado e, consequentemente, executado primeiro.

      Como um exemplo simples, digamos que você queira executar um comando, mas você precisa garantir que uma dependência seja instalada primeiro:

      package { 'python-software-properties':
          ensure => 'installed'
      }
      
      exec { 'add-repository':
          command => '/usr/bin/add-apt-repository ppa:ondrej/php5 -y'
          require => Package['python-software-properties']
      }
      

      A opção require recebe como parâmetro uma referência a outro recurso. Neste caso, estamos nos referindo ao recurso Package identificado como python-software-properties.

      É importante notar que, enquanto usamos exec, package, e assim por diante para declarar recurso (com letras minúsculas), quando nos referimos a recurso definidos anteriormente, usamos Exec, Package e assim por diante (em maiúsculas).

      Agora, digamos que você precise garantir que uma tarefa seja executada antes de outra. Para um caso como este, podemos usar a opção before:

      package { 'curl':
          ensure => 'installed'
          before => Exec['install script']
      }
      
      exec { 'install script':
          command => '/usr/bin/curl http://example.com/some-script.sh'
      

      Formato do Manifest

      Os manifests são basicamente uma coleção de declarações de recursos, usando a extensão .pp. Abaixo você pode encontrar um exemplo de um playbook simples que executa duas tarefas: atualiza o cache do apt e instala o vim posteriormente:

      exec { 'apt-get update':
          command => '/usr/bin/apt-get update'
      }
      
      package { 'vim':
          ensure => 'installed'
          require => Exec['apt-get update']
      }
      

      Antes do final deste guia, veremos um exemplo mais real de um manifest, explicado em detalhes. A próxima seção fornecerá uma visão geral dos elementos e recursos mais importantes que podem ser usados para escrever manisfests do Puppet.

      Escrevendo Manifests

      Trabalhando com Variáveis

      As variáveis podem ser definidas em qualquer ponto em um manifest. Os tipos mais comuns de variáveis são strings e matrizes ou arrays de strings, mas outros tipos também são suportados, como booleanos e hashes.

      O exemplo abaixo define uma variável string que é usada posteriormente dentro de um recurso:

      $package = "vim"
      
      package { $package:
         ensure => "installed"
      }
      

      Usando Loops

      Os loops são normalmente usados para repetir uma tarefa usando diferentes valores de entrada. Por exemplo, em vez de criar 10 tarefas para instalar 10 pacotes diferentes, você pode criar uma única tarefa e usar um loop para repetir a tarefa com todos os pacotes diferentes que deseja instalar.

      A maneira mais simples de repetir uma tarefa com valores diferentes no Puppet é usando arrays, como no exemplo abaixo:

      $packages = ['vim', 'git', 'curl']
      
      package { $packages:
         ensure => "installed"
      }
      

      A partir da versão 4, o Puppet suporta maneiras adicionais de iterar as tarefas. O exemplo abaixo faz a mesma coisa que o exemplo anterior, mas desta vez usando o iterador each. Esta opção oferece mais flexibilidade para fazer um loop pelas definições de recursos:

      $packages.each |String $package| {
        package { $package:
          ensure => "installed"
        }
      }
      

      Usando Condicionais

      Condicionais podem ser usadas para decidir dinamicamente se um bloco de código deve ou não ser executado, com base em uma variável ou em uma saída de um comando, por exemplo.

      O Puppet suporta a maioria das estruturas condicionais que você pode encontrar nas linguagens de programação tradicionais, como as instruções if/else e case. Além disso, alguns recursos como o exec suportam atributos que funcionam como condicionais, mas aceitam apenas uma saída de comando como condição.

      Digamos que você queira executar um comando com base em um fact. Nesse caso, como você deseja testar o valor de uma variável, é necessário usar uma das estruturas condicionais suportadas, como if/else:

      if $osfamily != 'Debian' {
       warning('This manifest is not supported on this OS.')
      }
      else {
       notify { 'Good to go!': }
      }
      

      Outra situação comum é quando você deseja condicionar a execução de um comando com base na saída de outro comando. Para casos como esse, você pode usar onlyif ou unless, como no exemplo abaixo. Este comando será executado apenas quando a saída de /bin/which php for bem-sucedida, ou seja, o comando sair com o status 0:

      exec { "Test":
       command => "/bin/echo PHP is installed here > /tmp/test.txt",
       onlyif => "/bin/which php"
      }
      

      Da mesma forma, unless irá executar o comando o tempo todo, exceto quando o comando sob unless sair com êxito:

      exec { "Test":
       command => "/bin/echo PHP is NOT installed here > /tmp/test.txt",
       unless => "/bin/which php"
      }
      

      Trabalhando com Templates

      Os templates geralmente são usados para definir arquivos de configuração, permitindo o uso de variáveis e outros recursos destinados a tornar esses arquivos mais versáteis e reutilizáveis. O Puppet suporta dois formatos diferentes para templates: Embedded Puppet (EPP) e Embedded Ruby (ERB). O formato EPP, no entanto, funciona apenas com versões recentes do Puppet (a partir da versão 4.0).

      Abaixo está um exemplo de um template ERB para configurar um virtual host no Apache, usando uma variável para configurar o document root para este host:

      <VirtualHost *:80>
          ServerAdmin webmaster@localhost
          DocumentRoot <%= @doc_root %>
      
          <Directory <%= @doc_root %>>
              AllowOverride All
              Require all granted
          </Directory>
      </VirtualHost>
      

      Para aplicar o template, precisamos criar um recurso file que processe o conteúdo do template com o método template. É assim que você aplicaria esse template para substituir o virtual host padrão do Apache:

      file { "/etc/apache2/sites-available/000-default.conf":
          ensure => "present",
          content => template("apache/vhost.erb") 
      }    
      

      O Puppet faz algumas suposições ao lidar com arquivos locais, a fim de reforçar a organização e a modularidade. Nesse caso, o Puppet procuraria um arquivo de template vhost.erb dentro de uma pasta apache/templates, dentro do seu diretório de módulos.

      Definindo e Acionando Serviços

      Os recursos de serviço são usados para garantir que os serviços sejam inicializados e ativados. Eles também são usados para acionar a reinicialização do serviço.

      Vamos levar em consideração o nosso exemplo anterior de uso de template, onde configuramos um virtual host no Apache. Se você deseja garantir que o Apache seja reiniciado após uma mudança no virtual host, primeiro crie um recurso service para o serviço Apache. É assim que esse recurso é definido no Puppet:

      service { 'apache2':
          ensure => running,
          enable => true
      }
      

      Agora, ao definir o recurso, você precisa incluir uma opção notify para acionar uma reinicialização:

      file { "/etc/apache2/sites-available/000-default.conf":
          ensure => "present",
          content => template("vhost.erb"),
          notify => Service['apache2'] 
      } 
      

      Manifest de Exemplo

      Agora, vamos dar uma olhada em um manifest que automatizará a instalação de um servidor Web Apache em um sistema Ubuntu 14.04, conforme discutido na introdução deste guia.

      O exemplo completo, incluindo o arquivo de template para configurar o Apache e um arquivo HTML para ser servido pelo servidor web, pode ser encontrado no Github. A pasta também contém um arquivo Vagrant que permite testar o manifest em uma configuração simplificada, usando uma máquina virtual gerenciada pelo Vagrant.

      Abaixo você pode encontrar o manifest completo:

      default.pp

      • $doc_root = "/var/www/example"
      • exec { 'apt-get update':
      • command => '/usr/bin/apt-get update'
      • }
      • package { 'apache2':
      • ensure => "installed",
      • require => Exec['apt-get update']
      • }
      • file { $doc_root:
      • ensure => "directory",
      • owner => "www-data",
      • group => "www-data",
      • mode => 644
      • }
      • file { "$doc_root/index.html":
      • ensure => "present",
      • source => "puppet:///modules/main/index.html",
      • require => File[$doc_root]
      • }
      • file { "/etc/apache2/sites-available/000-default.conf":
      • ensure => "present",
      • content => template("main/vhost.erb"),
      • notify => Service['apache2'],
      • require => Package['apache2']
      • }
      • service { 'apache2':
      • ensure => running,
      • enable => true
      • }

      Manifest Explicado

      linha 1

      O manifest começa com uma definição de variável, $doc_root. Essa variável é usada posteriormente em uma declaração de recurso.

      linhas 3-5

      Este recurso exec executa um comando apt-get update.

      linhas 7-10

      Este recurso package instala o pacote apache2, definindo que o recurso apt-get update é um requisito, o que significa que ele só será executado após a avaliação do recurso necessário.

      linhas 12-17

      Utilizamos um recurso file aqui para criar um novo diretório que servirá como document root. O recurso file pode ser usado para criar diretórios e arquivos, e também é usado para aplicar templates e copiar arquivos locais no servidor remoto. Esta tarefa pode ser executada em qualquer ponto do provisionamento, portanto, não precisamos definir nenhum require aqui.

      linhas 19-23

      Usamos outro recurso file aqui, desta vez para copiar nosso arquivo index.html local para o document root dentro do servidor. Usamos o parâmetro source para permitir que o Puppet saiba onde encontrar o arquivo original. Essa nomenclatura é baseada na maneira como o Puppet lida com arquivos locais; se você der uma olhada no repositório de exemplo do Github, você verá como a estrutura de diretórios deve ser criada para permitir que o Puppet encontre esse recurso. O diretório document root precisa ser criado antes da execução do recurso, e é por isso que incluímos uma opção require que faz referência ao recurso anterior.

      linhas 25-30

      Um novo recurso file é usado para aplicar o template do Apache e notificar o serviço para uma reinicialização. Neste exemplo, nosso provisionamento é organizado em um módulo chamado main, e é por isso que a origem do template é main/vhost.erb. Usamos uma instrução require para garantir que o recurso do template seja executado apenas após a instalação do pacote apache2, caso contrário, a estrutura de diretórios usada pelo Apache ainda não estará presente.

      linhas 32-35

      Finalmente, o recurso service declara o serviço apache2, que notificamos para reiniciar a partir do recurso que aplica o template de virtual host.

      Conclusão

      O Puppet é uma poderosa ferramenta de gerenciamento de configuração que usa uma DSL expressiva e personalizada para gerenciar recursos do servidor e automatizar tarefas. Sua linguagem oferece recursos avançados que podem dar flexibilidade extra às suas configurações de provisionamento; é importante lembrar que os recursos não são avaliados na mesma ordem em que são definidos e, por esse motivo, você precisa ter cuidado ao definir dependências entre os recursos para estabelecer a sequência de execução correta.

      No próximo guia desta série, veremos o Chef, outra ferramenta poderosa de gerenciamento de configuração que aproveita a linguagem de programação Ruby para automatizar a administração e o provisionamento da infraestrutura.



      Source link

      Gerenciamento de Configuração 101: Escrevendo Playbooks Ansible


      Introdução

      Em poucas palavras, o gerenciamento de configuração de servidor (também conhecido como IT Automation) é uma solução para transformar a sua administração de infraestrutura em uma base de código, descrevendo todos os processos necessários para fazer o deploy de um servidor em um conjunto de scripts de provisionamento que podem ser versionados e facilmente reutilizados. Isso pode melhorar muito a integridade de qualquer infraestrutura de servidor ao longo do tempo.

      Em um guia anterior, falamos sobre os principais benefícios da implementação de uma estratégia de gerenciamento de configuração para a infraestrutura de servidor, como as ferramentas de gerenciamento de configuração funcionam e o que essas ferramentas têm em comum normalmente.

      Esta parte da série o guiará pelo processo de automatização do provisionamento de servidores usando o Ansible, uma ferramenta de gerenciamento de configuração que fornece uma framework completo de automação e recursos de orquestração, enquanto mantém um objetivo final de simplicidade e minimalismo. Vamos nos concentrar na terminologia da linguagem, na sintaxe e nos recursos necessários para criar um exemplo simplificado para automatizar completamente o deployment de um servidor web Ubuntu 18.04 usando o Apache.

      A lista a seguir contém todas os passos que precisamos automatizar para alcançar nosso objetivo:

      1. Atualizar o cache do apt
      2. Instalar o Apache
      3. Criar um diretório raiz de documentos personalizado
      4. Colocar um arquivo index.html no diretório raiz de documentos personalizado
      5. Aplicar um template para configurar nosso virtual host personalizado
      6. Reiniciar o Apache

      Começaremos examinando a terminologia usada pelo Ansible, seguida de uma visão geral dos principais recursos da linguagem que podem ser usados para escrever playbooks. No final do guia, você encontrará o conteúdo de um exemplo completo de provisionamento para automatizar os passos descritos para configurar o Apache no Ubuntu 18.04.

      Nota: este guia tem como objetivo apresentar a você a linguagem do Ansible e como escrever playbooks para automatizar o provisionamento do seu servidor. Para uma visão mais introdutória do Ansible, incluindo as etapas necessárias para a instalação e sobre como inciar o uso dessa ferramenta, bem como a execução de comandos e playbooks do Ansible, consulte nosso guia Como instalar e configurar o Ansible no Ubuntu 18.04

      Iniciando

      Antes de podermos avançar para uma visão mais prática do Ansible, é importante nos familiarizarmos com importantes terminologias e conceitos introduzidos por essa ferramenta.

      Terminologia

      A lista a seguir contém uma rápida visão geral dos termos mais relevantes usados pelo Ansible:

      • Control Node: a máquina em que o Ansible está instalado, responsável pela execução do provisionamento nos servidores que você está gerenciando.
      • Inventory: um arquivo INI que contém informações sobre os servidores que você está gerenciando.
      • Playbook: um arquivo YAML contendo uma série de procedimentos que devem ser automatizados.
      • Task: um bloco que define um único procedimento a ser executado, por exemplo: instalar um pacote.
      • Module: um módulo normalmente abstrai uma tarefa do sistema, como lidar com pacotes ou criar e alterar arquivos. O Ansible possui diversos módulos integrados, mas você também pode criar módulos personalizados.
      • Role: um conjunto de playbooks, modelos e outros arquivos relacionados, organizados de maneira predefinida para facilitar a reutilização e o compartilhamento.
      • Play: um provisionamento executado do início ao fim é chamado de play.
      • Facts: variáveis globais que contêm informações sobre o sistema, como interfaces de rede ou sistema operacional.
      • Handlers: usado para acionar alterações no status do serviço, como reiniciar ou recarregar um serviço.

      Formato da Task

      Uma task ou tarefa define uma única etapa automatizada que deve ser executada pelo Ansible. Geralmente envolve o uso de um módulo ou a execução de um comando raw. É assim que uma tarefa se parece:

      - name: Esta é uma task
        apt: name=vim state=latest
      

      A parte do name é opcional na verdade, mas recomendada, pois aparece na saída do provisionamento quando a tarefa é executada. A parte do apt é um módulo Ansible embutido que abstrai o gerenciamento de pacotes em distribuições baseadas no Debian. Este exemplo de tarefa informa ao Ansible que o pacote vim deve ter seu estado alterado para latest, o que fará com que o gerenciador de pacotes instale este pacote, caso ainda não esteja instalado.

      Formato do Playbook

      Playbooks são arquivos YAML que contêm uma série de diretivas para automatizar o provisionamento de um servidor. O exemplo a seguir é um playbook simples que executa duas tasks: atualiza o cache do apt e instala o vim posteriormente:

      ---
      - hosts: all
        become: true
        tasks:
           - name: Update apt-cache 
             apt: update_cache=yes
      
           - name: Install Vim
             apt: name=vim state=latest
      

      O YAML depende da identação para serializar estruturas de dados. Por esse motivo, ao escrever playbooks e, especialmente, ao copiar exemplos, você precisa ter cuidado extra para manter a identação correta.

      Antes do final deste guia, veremos um exemplo mais real de um playbook, explicado em detalhes. A próxima seção fornecerá uma visão geral dos elementos e recursos mais importantes que podem ser usados para escrever playbooks do Ansible.

      Escrevendo Playbooks

      Agora que você conhece a terminologia básica e o formato geral dos playbooks e tasks no Ansible, aprenderemos sobre alguns recursos do playbook que podem nos ajudar a criar automações mais versáteis.

      Trabalhando com Variables

      Existem diferentes maneiras pelas quais você pode definir variáveis no Ansible. A maneira mais simples é usar a seção vars de um playbook. O exemplo abaixo define uma variável package que mais tarde é usada dentro de uma task:

      ---
      - hosts: all
        become: true
        vars:
           package: vim
        tasks:
           - name: Install Package
             apt: name={{ package }} state=latest
      

      A variável package possui um escopo global, o que significa que pode ser acessada a partir de qualquer ponto do provisionamento, mesmo a partir dos arquivos e modelos incluídos.

      Usando Loops

      Os loops são normalmente usados para repetir uma task usando diferentes valores de entrada. Por exemplo, em vez de criar 10 tasks para instalar 10 pacotes diferentes, você pode criar uma única task e usar um loop para repetir a tarefa com todos os pacotes diferentes que deseja instalar.

      Para criar um loop dentro de uma task, inclua a opção with_items com uma matriz de valores. O conteúdo pode ser acessado através da variável de loop item, conforme mostrado no exemplo abaixo:

      - name: Install Packages
        apt: name={{ item }} state=latest
        with_items:
           - vim
           - git
           - curl  
      

      Você também pode usar uma variável matriz para definir seus itens:

      ---
      - hosts: all
        become: true
        vars:
           packages: [ 'vim', 'git', 'curl' ]
        tasks:
           - name: Install Package
             apt: name={{ item }} state=latest
             with_items: "{{ packages }}"
      

      Usando Condicionais

      Condicionais podem ser usados para decidir dinamicamente se uma tarefa deve ou não ser executada, baseado em uma variável ou em uma saída de um comando, por exemplo.

      O exemplo a seguir vai encerrar apenas os sistemas baseados em Debian:

      - name: Shutdown Debian Based Systems
        command: /sbin/shutdown -t now
        when: ansible_os_family == "Debian"
      

      O condicional when recebe como argumento uma expressão a ser avaliada. A task é executada apenas no caso de a expressão ser avaliada como true. Em nosso exemplo, testamos um fact para verificar se o sistema operacional é da família Debian.

      Um caso de uso comum para condicionais na automação de TI é quando a execução de uma task depende da saída de um comando. Com o Ansible, a maneira como implementamos isso é registrando uma variável para armazenar os resultados de uma execução de comando e testando essa variável em uma tarefa subseqüente. Podemos testar o status de saída do comando (se houve falha ou êxito). Também podemos verificar um conteúdo específico dentro da saída, embora isso possa exigir o uso de expressões regulares (regex) e comandos de parsing de string.

      O próximo exemplo mostra duas tarefas condicionais baseadas na saída de um comando php -v. Testaremos o status de saída do comando, pois sabemos que ele falhará na execução caso o PHP não esteja instalado neste servidor. A parte ignore_errors da task é importante para garantir que o provisionamento continue mesmo quando o comando falha na execução.

      - name: Check if PHP is installed
        register: php_installed
        command: php -v
        ignore_errors: true
      
      - name: This task is only executed if PHP is installed
        debug: var=php_install
        when: php_installed|success
      
      - name: This task is only executed if PHP is NOT installed
        debug: msg='PHP is NOT installed'
        when: php_installed|failed
      

      O módulo debug usado aqui é um módulo útil para mostrar o conteúdo de variáveis ou mensagens de depuração. Ele pode imprimir uma string (ao usar o argumento msg) ou imprimir o conteúdo de uma variável (ao usar o argumentovar).

      Trabalhando com Templates

      Os templates são usados geralmente para configurar arquivos de configuração, permitindo o uso de variáveis e outros recursos destinados a tornar esses arquivos mais versáteis e reutilizáveis. O Ansible usa o mecanismo de template Jinja2.

      O exemplo a seguir é um template para configurar um virtual host no Apache, usando uma variável para configurar a raiz de documentos para este host:

      <VirtualHost *:80>
          ServerAdmin webmaster@localhost
          DocumentRoot {{ doc_root }}
      
          <Directory {{ doc_root }}>
              AllowOverride All
              Require all granted
          </Directory>
      </VirtualHost>
      

      O módulo interno template é usado para aplicar o template a partir de uma tarefa. Se você nomeou o arquivo de template acima de vhost.tpl e o colocou no mesmo diretório que seu playbook, é assim que você aplicaria o template para substituir o virtual host padrão do Apache:

      - name: Change default Apache virtual host
        template: 
          src: vhost.tpl
          dest: /etc/apache2/sites-available/000-default.conf
      

      Definindo e Acionando Handlers

      Handlers são usados para acionar uma alteração de estado em um serviço, como um restart ou um stop. Mesmo que possam parecer bastante semelhantes às tasks regulares, os handlers são executados apenas quando acionados anteriormente a partir de uma diretiva notify em uma task. Eles geralmente são definidos como uma matriz em uma seção handlers do playbook, mas também podem estar em arquivos separados.

      Vamos levar em consideração o nosso exemplo anterior de uso de templates, onde configuramos um virtual host no Apache. Se você deseja garantir que o Apache seja reiniciado após uma alteração no virtual host, primeiro você precisa criar um handler para o serviço Apache. É assim que os handlers são definidos dentro de um playbook:

      handlers:
          - name: restart apache
            service: name=apache2 state=restarted
      
          - name: other handler
            service: name=other state=restarted
      

      A diretiva name aqui é importante porque ela será o identificador exclusivo desse handler. Para acionar esse handler a partir de uma tarefa, você deve usar a opção notify:

      - name: Change default Apache virtual host
        template: 
          src: vhost.tpl
          dest: /etc/apache2/sites-available/000-default.conf
        notify: restart apache
      

      Vimos alguns dos recursos mais importantes que você pode usar para começar a escrever playbooks Ansible. Na próxima seção, veremos um exemplo mais real de um playbook que automatizará a instalação e configuração do Apache no Ubuntu.

      Playbook de Exemplo

      Agora, vamos dar uma olhada em um playbook que automatizará a instalação de um servidor web Apache dentro de um sistema Ubuntu 18.04, conforme discutido na introdução deste guia.

      O exemplo completo, incluindo o arquivo de template para configurar o Apache e um arquivo HTML para ser servido pelo servidor web, pode ser encontrado no Github. A pasta também contém um arquivo Vagrant que permite testar o playbook em uma configuração simplificada, usando uma máquina virtual gerenciada pelo Vagrant.

      Conteúdo do Playbook

      O conteúdo completo do playbook está disponível aqui para sua conveniência:

      playbook.yml

      • ---
      • - hosts: all
      • become: true
      • vars:
      • doc_root: /var/www/example
      • tasks:
      • - name: Update apt
      • apt: update_cache=yes
      • - name: Install Apache
      • apt: name=apache2 state=latest
      • - name: Create custom document root
      • file: path={{ doc_root }} state=directory owner=www-data group=www-data
      • - name: Set up HTML file
      • copy: src=index.html dest={{ doc_root }}/index.html owner=www-data group=www-data mode=0644
      • - name: Set up Apache virtual host file
      • template: src=vhost.tpl dest=/etc/apache2/sites-available/000-default.conf
      • notify: restart apache
      • handlers:
      • - name: restart apache
      • service: name=apache2 state=restarted

      Vamos examinar cada parte deste playbook em mais detalhes:

      hosts: all
      O playbook começa afirmando que deve ser aplicado a todos os hosts em seu inventário (hosts: all). É possível restringir a execução do playbook a um host específico ou a um grupo de hosts. Esta opção pode ser substituída no momento da execução.

      become: true
      A parte become: true diz ao Ansible para usar a escalação de privilégios (sudo) para executar todas as tarefas neste playbook. Esta opção pode ser substituída em uma base de task por task.

      vars
      Define uma variável, doc_root, que é usada posteriormente em uma task. Esta seção pode conter várias variáveis.

      tasks
      A seção em que as tasks reais são definidas. A primeira task atualiza o cache do apt e a segunda task instala o pacote apache2.

      A terceira task usa o módulo interno file para criar um diretório para servir como a raiz de documentos. Este módulo pode ser usado para gerenciar arquivos e diretórios.

      A quarta task usa o módulo copy para copiar um arquivo local para o servidor remoto. Estamos copiando um arquivo HTML simples para servir como nosso site hospedado pelo Apache.

      handlers
      Finalmente, temos a seção handlers, onde os serviços são declarados. Definimos o handler restart apache que é notificado a partir da quarta tarefa, onde o template Apache é aplicado.

      Executando um Playbook

      Depois de baixar o conteúdo deste playbook para o control node Ansible, você pode usar o ansible-playbook para executá-lo em um ou mais nodes do seu inventário. O comando a seguir executará o playbook em todos os hosts do seu arquivo de inventário padrão, usando a autenticação de par de chaves SSH para conectar-se como o usuário atual do sistema:

      • ansible-playbook playbook.yml

      Você também pode usar -l para limitar a execução a um único host ou a um grupo de hosts do seu inventário:

      • ansible-playbook -l host_ou_grupo playbook.yml

      Se você precisar especificar um usuário SSH diferente para se conectar ao servidor remoto, poderá incluir o argumento -u user nesse comando:

      • ansible-playbook -l host_ou_grupo playbook.yml -u usuário_remoto

      Para obter mais informações sobre como executar comandos Ansible e playbooks, consulte o nosso guia Como instalar e configurar o Ansible no Ubuntu 18.04.

      Conclusão

      O Ansible é uma ferramenta minimalista de automação de TI que possui uma baixa curva de aprendizado, usando o YAML para seus scripts de provisionamento. Ele possui um grande número de módulos internos que podem ser usados para abstrair tasks, como instalar pacotes e trabalhar com templates. Seus requisitos simplificados de infraestrutura e linguagem simples podem ser uma boa opção para quem está iniciando o gerenciamento de configurações. No entanto, pode haver alguns recursos avançados que você pode encontrar em ferramentas mais complexas, como Puppet e Chef.

      Na próxima parte desta série, veremos uma visão geral prática do Puppet, uma ferramenta de gerenciamento de configuração popular e bem estabelecida que usa uma DSL personalizada expressiva e poderosa baseada no Ruby para escrever scripts de provisionamento.



      Source link