Chef: Uso De Condicionais Not_if E Only_if

| Comments

Pare e volte uma casa

Se você nunca utilizou ou sabe pouco sobre o Chef é importante que você pare aqui mesmo e volte uma casa. Sugiro que leia o post Chef: Automação e Gerenciamento de Configuração antes de seguir este, uma vez que através dele você entenderá o que é, e como funciona o Chef, bem como sua instalação básica.

Resources

Conforme dito no post anterior, resource é uma descrição de estado que:

  • Descreve o estado desejado para um item de configuração
  • Declara os passos necessários para levar o item especificado ao estado desejado
  • Especifica o tipo de resource – por exemplo, package, template ou service
  • Lita detalhes adicionais (também conhecidos como propriedades de resources), conforme necessário
  • São agrupados em recipes, que descrevem configurações em geral

Utilizando (Guardas) not_if e only_if

Todos os resources (incluindo os personalizados) no Chef compartilham um conjunto de opções comuns: ações, propriedades, condicionais, notificações e paths relativos.

Guards

Propriedades de guarda, ou guards, como são chamadas no Chef, podem ser utilizadas para avaliar o estado de um node durante a fase de execução do chef-client. Esta avaliação funciona como uma condicional e, baseando-se nos resultados da mesma, a propriedade guard é então utilizada para indicar ao chef-client se ele deve ou não continuar a execução de um resource. Uma propriedade guard aceita tanto um valor string quanto um bloco de código Ruby.

  • A string é executada como um comando shell. Caso o comando retorne 0 (true), a propriedade guard é aplicada. Caso o comando retorne qualquer outro valor a propriedade guard não será aplicada.
  • Um bloco é executado como um código Ruby que deve retornar true ou false. Da mesma forma, caso o comando retorne true, a propriedade guard é aplicada. Caso o comando retorne false a propriedade guard não será aplicada.

Uma guard é importante para garantir que um resource seja idempotente ao permitir um teste no próprio resource certificando-se de que o mesmo se encontra no estado desejado de forma que o chef-client não faça nada.

Atributos

Os seguintes atributos podem ser utilizados para definir uma guard que é avaliada durante a fase de execução do chef-client:

not_ifImpede a execução de um resource quando a condição retornar true.

only_if – Permite a execução de um resource apenas quando a condição retornar true.

Mãos à obra

Para simplificar seguirei utilizando a recipe utilizada no post anterior. Não sabe o que é uma recipe? -> Novamente, caso não entenda o que estou dizendo, volte uma casa e leia o post anterior, no qual explico o que é uma recipe, bem como cada elemento da mesma, visto que a utilizaremos aqui.

Nossa recipe era a seguinte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package 'apache' do
        package_name 'httpd'
        action :install
end

service 'httpd' do
        action [:enable, :start]
end

file '/var/www/html/index.html' do
        content 'Hello World!'
        mode '0755'
        owner 'root'
        group 'apache'
end

Como primeira alteração vamos editar o arquivo /etc/motd. Este arquivo nada mais é do que a definição de um baner que será apresentado sempre que alguém se logar em seu servidor.

Por padrão, este arquivo costuma vir vazio. Este é o caso da máquina utilizada para este exemplo:

1
2
[root@kalib6 ~]# cat /etc/motd
[root@kalib6 ~]#

Começaremos definindo o conteúdo que deverá existir em nosso arquivo /etc/motd incluindo um resource do tipo file no final de nossa recipe exemplo.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 'apache' do
        package_name 'httpd'
        action :install
end

service 'httpd' do
        action [:enable, :start]
end

file '/var/www/html/index.html' do
        content 'Hello World!'
        mode '0755'
        owner 'root'
        group 'apache'
end

file '/etc/motd' do
        content 'Bem Vindo!'
end

Até aqui nenhuma novidade (caso você tenha lido de fato o post anterior ou já tenha utilizado Chef anteriormente), apenas incluímos mais um resource em nossa recipe exemplo.rb informando que o arquivo /etc/motd deve existir e que seu conteúdo deverá ser Bem Vindo!.

Validando e executando localmente nossa recipe conforme feito anteriormente veremos que todos os demais passos ou resources serão ignorados por já estarem estarem no estado desejado (idempotência), restando apenas a inclusão do conteúdo no /etc/motd:

Validando

1
2
3
4
5
6
7
[root@kalib6 ~]# ruby -c exemplo.rb && foodcritic exemplo.rb
Syntax OK
Checking 1 files
x
FC011: Missing README in markdown format: ../README.md:1
FC031: Cookbook without metadata.rb file: ../metadata.rb:1
FC071: Missing LICENSE file: ../LICENSE:1

Executando

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T22:03:26+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T22:03:26+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T22:03:29+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 4 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create (up to date)
  * file[/etc/motd] action create
    - update content in file /etc/motd from e3b0c4 to f13843
    --- /etc/motd       2018-04-15 20:51:00.411479476 +0000
    +++ /etc/.chef-motd20180415-1681-1p1fm7m    2018-04-15 22:03:42.142091791 +0000
    @@ -1 +1,2 @@
    +Bem Vindo!
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 1/5 resources updated in 15 seconds

Para termos certeza de que o nosso arquivo foi corretamente alterado…

1
2
[root@kalib6 ~]# cat /etc/motd
Bem Vindo!

Novamente, nenhuma novidade até aqui.

Vamos agora utilizar o tipo de resource execute, o qual nos permite executar um comando a cada execução do chef-client. Vale lembrar que uma das características do Chef é a idempotência, portanto o execute pode ser considerado uma exceção, já que o comando será executado sempre, mesmo que já tenha sido executado anteriormente. E é neste tipo de situação que os guards se mostram importantes.

Comecemos inserindo um resource do tipo execute em nossa recipe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package 'apache' do
        package_name 'httpd'
        action :install
end
https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
service 'httpd' do
        action [:enable, :start]
end

file '/var/www/html/index.html' do
        content 'Hello World!'
        mode '0755'
        owner 'root'
        group 'apache'
end

file '/etc/motd' do
        content 'Bem Vindo!'
end

execute 'meu-comando' do
        command 'echo " Obrigado!" >> /etc/motd'
        only_if 'test -r /etc/motd'
end

O que adicionamos aqui?

  • Resource Type: execute (Para que possamos executar um comando)
  • Resource Name: meu-comando (Poderíamos ter utilizado qualquer nome)
  • Command: ‘echo “ Obrigado!” >> /etc/motd’ (O comando que desejamos executar. Estou utilizando echo para inserir * Obrigado!* ao meu arquivo /etc/motd)
  • Guard: only_if ‘test -r /etc/motd’ (only_if implica que meu resource meu-comando será executado apenas caso o resultado de test -r /etc/motd seja positivo. test -r irá verificar se o arquivo /etc/motd existe no sistema. Caso sim, meu comando echo " Obrigado!" >> /etc/motd será executado conforme planejado, do contrário será ignorado)

Nós já sabemos que o arquivo existe, portanto esperamos que * Obrigado!* seja adicionada ao mesmo após execução do chef-client.

Verificando nosso código

1
2
3
4
5
6
7
[root@kalib6 ~]# ruby -c exemplo.rb && foodcritic exemplo.rb
Syntax OK
Checking 1 files
x
FC011: Missing README in markdown format: ../README.md:1
FC031: Cookbook without metadata.rb file: ../metadata.rb:1
FC071: Missing LICENSE file: ../LICENSE:1

Tudo ok. Executando…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:08:54+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:08:54+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:09:03+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 5 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create (up to date)
  * file[/etc/motd] action create (up to date)
  * execute[meu-comando] action run
    - execute echo " Obrigado!" >> /etc/motd

Running handlers:
Running handlers complete
Chef Client finished, 1/6 resources updated in 31 seconds

Repare que o Chef executou o comando para inserir Obrigado!.

1
2
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!

Conforme dito anteriormente, o resource execute irá executar o meu comando SEMPRE que o chef-client rodar. Vejamos o que acontece ao executar novamente o chef-client sem alterar a recipe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:18:10+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:18:10+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:18:20+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 5 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create (up to date)
  * file[/etc/motd] action create
    - update content in file /etc/motd from 201ddf to f13843
    --- /etc/motd       2018-04-15 23:16:13.705005377 +0000
    +++ /etc/.chef-motd20180415-2432-zhtjnq     2018-04-15 23:18:42.138379629 +0000
    @@ -1,2 +1,2 @@
    -Bem Vindo! Obrigado!
    +Bem Vindo!
    - restore selinux security context
  * execute[meu-comando] action run
    - execute echo " Obrigado!" >> /etc/motd

Running handlers:
Running handlers complete
Chef Client finished, 2/6 resources updated in 31 seconds

Como em nossa recipe temos o resource file que determina o conteúdo do arquivo /etc/motd como sendo Bem Vindo!, o chef percebeu que o arquivo se encontrava diferente, pois acrescentamos o Obrigado! no passo anterior. O arquivo foi corrigido, voltando a ter seu conteúdo original, em seguida o Chef inseriu novamente Obrigado!, pois assim está determinado em nossa recipe.

É fácil perceber isto nas linhas a seguir, retiradas do resultado de nossa última execução do chef-client:

1
2
3
4
5
6
7
8
9
10
* file[/etc/motd] action create
  - update content in file /etc/motd from 201ddf to f13843
  --- /etc/motd       2018-04-15 23:16:13.705005377 +0000
  +++ /etc/.chef-motd20180415-2432-zhtjnq     2018-04-15 23:18:42.138379629 +0000
  @@ -1,2 +1,2 @@
  -Bem Vindo! Obrigado!
  +Bem Vindo!
  - restore selinux security context
* execute[meu-comando] action run
  - execute echo " Obrigado!" >> /etc/motd

Se verificarmos nosso arquivo, veremos que ele está da mesma forma:

1
2
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!

Para percebermos a diferença, vamos comentar as linhas do resource file /etc/motd:

1
2
3
4
5
...
#file '/etc/motd' do
#       content 'Bem Vindo!'
#end
...

Ao comentar estas linhas, nossa recipe não mais indicará que o conteúdo do arquivo /etc/motd deve ser Bem Vindo!, portanto vejamos o que acontece quando executamos novamente o chef-client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:28:32+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:28:33+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:28:42+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 4 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create (up to date)
  * execute[meu-comando] action run
    - execute echo " Obrigado!" >> /etc/motd

Running handlers:
Running handlers complete
Chef Client finished, 1/5 resources updated in 32 seconds

Uma vez que nosso arquivo /etc/motd já possuía o conteúdo Bem Vindo! Obrigado! e desta vez não tentou garantir que o conteúdo do mesmo fosse apenas Bem Vindo!, vejamos como ele se encontra:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!
 Obrigado!
 ```

 **C**omo esperado, inserimos mais um ` Obrigado!` ao arquivo. Se executarmos novamente o chef-client, estaremos acrescentando novamente um ` Obrigado!` ao arquivo, pois o mesmo existe e não estamos mais tentando validar seu conteúdo.

 **E** se removermos manualmente o arquivo `/etc/motd`?

 ```
[root@kalib6 ~]# rm /etc/motd
rm: remove regular file ‘/etc/motd’? y
[root@kalib6 ~]# cat /etc/motd
cat: /etc/motd: No such file or directory

Vejamos o que o chef-client fará:

1
2
3
... (Ignorando linhas desnecessárias)
  * execute[meu-comando] action run (skipped due to only_if)
...

Como já tínhamos comentado as linhas que garantem que o /etc/motd existe e possui o conteúdo Bem Vindo!, o resource que incluiría Obrigado! foi ignorado, pois em nossa guard temos a condição only_if, que indica que o comando só será executado SE o arquivo /etc/motd existir. Para ter certeza disto, vamos verificar:

1
2
[root@kalib6 ~]# cat /etc/motd
cat: /etc/motd: No such file or directory

Aproveitando a situação, vamos alterar nossa recipe e utilizar not_if ao invés de only_if:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package 'apache' do
        package_name 'httpd'
        action :install
end

service 'httpd' do
        action [:enable, :start]
end

file '/var/www/html/index.html' do
        content 'Hello World!'
        mode '0755'
        owner 'root'
        group 'apache'
end

#file '/etc/motd' do
#       content 'Bem Vindo!'
#end

execute 'meu-comando' do
        command 'echo " Obrigado!" >> /etc/motd'
        not_if 'test -r /etc/motd'
end

Já sabemos que o arquivo não existe, portanto a nossa regra agora será satisfeita, visto que queremos adicionar o conteúdo Obrigado! APENAS caso o arquivo NÃO exista.

Vale lembrar que, por padrão, o comando echo ' Obrigado!' >> /etc/motd irá adicionar o conteúdo ao arquivo e, caso o mesmo não exista, ele será criado com o determinado conteúdo. Isto não é um recurso do Chef, mas sim do próprio linux/echo. Executando nosso chef-client:

1
2
3
4
5
6
... (Ignorando linhas desnecessárias)
* execute[meu-comando] action run
  - execute echo " Obrigado!" >> /etc/motd

Running handlers:
Running handlers complete

Repare que o arquivo foi criado apenas com o conteúdo Obrigado!, conforme descrito em nossa recipe.

1
2
[root@kalib6 ~]# cat /etc/motd
 Obrigado!

Caso o chef-client seja executado novamente, nada acontecerá, uma vez que o resource atual indica que o comando APENAS deverá ser executado caso o arquivo NÃO exista.

1
2
3
... (Ignorando linhas desnecessárias)
  * execute[meu-comando] action run (skipped due to not_if)
...

Simples, certo?! É importante lembrar que o Chef executará os resources na devida ordem em que forem listados na recipe, portanto é importante alinhar todas as instruções e resources de acordo com o resultado desejado.

Happy hacking!

Chef: Automação E Gerenciamento De Configuração

| Comments

Uma coisa de cada vez

Chef é uma popular ferramenta de Gerenciamento de Configurações criado pela empresa de mesmo nome, Chef. O Chef é desenvolvido em Ruby e Erlang, e utiliza uma linguagem DSL (domain-specific language) em Ruby puro para escrever arquivos de configuração de sistemas chamados “recipes” (receitas).

Antes de falarmos sobre o Chef é importante entender primeiramente o conceito e a utilidade de ferramentas de Gerenciamento de Configuração.

Gerenciamento de Configuração

Gerenciamento de Configuração, ou CM (Configuration Management), é um processo de engenharia de sistemas que visa garantir a consistência entre ativos físicos e lógicos em um ambiente operacional. O processo de gerenciamento de configuração busca identificar e rastrear itens individuais de configuração, documentando capacidades funcionais e interdependências. Administradores, técnicos e desenvolvedores de sistemas podem utilizar ferramentas de Gerenciamento de Configuração para verificar o efeito que uma mudança em um item de configuração terá em outros sistemas.

Em palavras simples, o objetivo de uma ferramenta de gerenciamento de configuração é simplificar a vida de quem administra serviços e sistemas garantindo uma uniformidade no quesito configuração.

Como exemplo prático e simplista, imagine um servidor web que será responsável por hospedar um pequeno site em php. Este servidor possuirá alguns atributos/aplicativos/configurações, tais como:

  • Apache instalado;
  • Alterações específicas nos arquivos de configuração do Apache;
  • Serviço do Apache ativo e iniciado;
  • Arquivos referentes ao site em si em um diretório específico;
  • Permissões específicas atribuídas ao diretório específico do site;
  • etc…

Configurar tudo isso manualmente em um único servidor é simples. Você poderia conectar-se via SSH no servidor, instalar o apache com o gerenciador de pacotes da distribuição utilizada, configurar o que for necessário no apache, iniciar o serviço, etc, etc, etc. Mas o que fazer quando sua infraestrutura cresce? Quando se quer maior disponibilidade do site, quando agora você roda este site em um cluster com 5 servidores?

Basicamente a mesma coisa, certo? Você pode se conectar em cada um dos servidores e repetir os mesmos passos. O problema se dá justamente nessa repetição de passos, onde você pode cometer erros, perder tempo, etc. Além disso, o que acontece se um colega seu modificar algo em um dos servidores e não lhe avisar? E se ele esquecer de replicar esta mudança nos demais?

É fácil achar diversas razões pelas quais torna-se difícil administrar e gerenciar configurações em ambientes mais complexos. A ideia por trás de uma ferramenta de Gerenciamento de Configuração é justamente reduzir esta complexidade, eliminando a necessidade de conectar-se manualmente à diversos servidores para aplicar as mesmas rotinas, passos e configurações.

Através de arquivos de texto podemos literalmente descrever o estado e as ações desejadas para nossos serviços e sistemas. Por exemplo: Posso dizer que possuo um grupo de servidores chamado WebServer, o qual contém 10 servidores com o Sistema Operacional CentOS 7. Posso incluir a informação de que preciso que todos eles estejam com a versão X do Apache instalada e que o serviço esteja ativo e rodando. Além disso, posso dizer que desejo que exista no diretório /var/www/meusite/ todo o conteúdo que está em um mapeamento de rede específico, ou mesmo em um repositório que possuo no github.

Ao invés de me conectar em cada um dos 10 servidores para fazer tudo isso, um simples comando será o suficiente. O comando em específico dependerá da solução adotada, visto que existem diversas ferramentas de CM (Gerenciamento de Configuração). Mas, basicamente, ele irá ler o(s) arquivo(s) de “instruções” que nós definimos e saberá em quais servidores ele deverá instalar o Apache e configurar de acordo com o especificado, nos dando apenas o resultado final em forma de relatório simples.

E se alguém da equipe alterar um arquivo de configuração diretamente em um dos servidores? A ferramenta, em sua próxima execucação, irá identificar que o estado desejado para aquele meu grupo de servidores está diferente em um dos servidores. Ela então modificará aquele arquivo específico naquele servidor para que ele volte ao seu estado desejado.

Além desta segurança, nós agora passamos a ter um ponto único de modificação. Ao desejarmos mudar algo, o faremos apenas no nosso “código”, ao invés de o fazer nos 10 servidores manualmente.

O mesmo benefício se dá em caso de erros e falhas: um ponto único de correção.

Com este mecanismo de descrever o estado de nossa infraestrutura em arquivos de texto/código, entramos em um novo conceito: Infrastructure as Code, ou Infraestrutura como Código. Uma vez que temos nossa infraestrutura em formato de código, podemos literalmente versionar e gerenciar nossa infraestrutura com repositórios Git, por exemplo.

O que é Chef?

Conforme dito mais acima, Chef é uma das mais populares ferramentas de Gerenciamento de Configuração disponíveis atualmente. É compatível e facilmente integrado à plataformas de computação em nuvem, tais como Internap, Amazon EC2, Google Cloud Platform, OpenStack, SoftLayer, Microsoft Azure e Rackspace, provendo e configurando servidores automaticamente.

O usuário escreve “recipes” (receitas) que descrevem como o Chef deve gerenciar aplicações, servidores e utilitários. Estas “recipes”, as quais podem ser agrupadas em “cookbooks” (livros de receitas) descrevem uma série de recursos que devem estar em um determinado estado. Este recursos podem ser pacotes, serviços ou mesmo arquivos.

Chef pode rodar em um modo cliente/servidor ou standalone, com o chamado “chef-solo”. No modo cliente/servidor, o cliente Chef envia uma série de atributos sobre o node ou cliente/host para o Chef server. O Chef server utiliza-se da ferramenta Solr para indexar estes atributos e provê uma API na qual os clientes podem fazer consultas. As recipes podem fazer requisições à esta base de atributos e utilizar os dados resultantes para configurar o cliente ou node.

Embora inicialmente o Chef fosse utilizado para gerenciar exclusivamente máquinas Linux, as versões mais atuais também suportam máquinas Windows.

A ideia para este post é dar uma breve introdução ao Chef, portanto não vou entrar em maiores detalhes do funcionamento por hora.

Resources

Como dito antes, um Resource é uma descrição de estado desejado para um determinado item. Estes resources são gerenciados através de recipes.

Um resource possui basicamente 4 componentes fundamentais que são definidos em um bloco de código Ruby:

  • Resource Type – Tipo de resource (Pode ser um pacote, serviço, arquivo…)
  • Resource Name – Nome do resource
  • Resource Properties – Propriedades do resource
  • Actions – Ações a serem aplicadas ao resource

Um exemplo de recipe para instalar o Apache em um servidor Ubuntu, por exemplo, seria o seguinte:

1
2
3
package 'httpd' do
    action :install
end

No exemplo acima temos o tipo de resource como sendo “package”, o nome do resource como sendo “httpd” e a ação “install”.

O que vai acontecer aqui? Simples de entender, certo? O pacote httpd (Apache) será instalado. Mas o que acontece caso o pacote httpd já esteja instalado?

Nada. Uma das características do Chef é a idempotência. Na matemática e ciência da computação, a idempotência é a propriedade que algumas operações têm de poderem ser aplicadas várias vezes sem que o valor do resultado se altere após a aplicação inicial. Ou seja, O Chef primeiramente confere se o estado desejado já está aplicado e, caso sim, ignora aquela instrução.

Novamente… A ideia para este post é dar uma breve introdução ao Chef, portanto não vou entrar em maiores detalhes sobre os tipos de resources e suas possíveis ações. Vamos ao que interessa…

Instalando o Chef

Conforme explicado acima, o Chef pode ser utilizado em modo cliente/servidor ou standalone. Para esta introdução utilizaremos o modo standalone ou local para simplificar as coisas.

Para instalar podemos utilizar o gerenciador de pacotes da distribuição Linux que utilizamos ou baixando o chefdk (Development Kit) através da página de downloads do chef.

Arch Linux

No meu caso, utilizarei o pacote chef-dk existente para o Arch Linux, mas sinta-se livre para baixar diretamente no site e executar o pacote de acordo com sua distribuição.

1- Baixar o pacote do AUR:

1
$ wget https://aur.archlinux.org/cgit/aur.git/snapshot/chef-dk.tar.gz

2- Descompactar e Compilar:

1
2
3
4
5
$ tar -xvzf chef-dk.tar.gz

$ cd chef-dk

$ makepkg

3- Instalar o pacote:

1
$ sudo pacman -U chef-dk-2.5.3-1-x86_64.pkg.tar.xz

4- Confirmar que deu tudo certo:*

1
2
3
4
5
6
7
8
$ chef --version

Chef Development Kit Version: 2.5.3
chef-client version: 13.8.5
delivery version: master (73ebb72a6c42b3d2ff5370c476be800fee7e5427)
berks version: 6.3.1
kitchen version: 1.20.0
inspec version: 1.51.21

Centos, Debian, Ubuntu…

No CentOS, Ubuntu, Debian ou outras distribuições, o procedimento será relativamente parecido, portanto vejamos como seria no caso do CentOS baixando o arquivo diretamente do site de downloads:

1- Baixar o arquivo .rpm para Red Hat: Aqui

1
$ wget https://packages.chef.io/files/stable/chefdk/2.5.3/el/7/chefdk-2.5.3-1.el7.x86_64.rpm

2- Instalar via RPM:

1
$ sudo rpm -ivh chefdk-2.5.3-1.el7.x86_64.rpm

3- Confirmar que deu tudo certo:

1
2
3
4
5
6
7
8
$ chef --version

Chef Development Kit Version: 2.5.3
chef-client version: 13.8.5
delivery version: master (73ebb72a6c42b3d2ff5370c476be800fee7e5427)
berks version: 6.3.1
kitchen version: 1.20.0
inspec version: 1.51.21

Testando o Chef Localmente

Para facilitar o entendimento, vamos criar uma recipe simples para aplicarmos localmente.

Vamos começar criando um arquivo chamado exemplo.rb com o seguinte conteúdo:

1
2
3
4
package 'apache' do
        package_name 'httpd'
        action :install
end

O que temos aqui?

  • Resource Type: package (Pois queremos instalar o pacote httpd)
  • Resource Name: apache (Embora o nome do pacote no CentOS seja httpd, o nome do nosso resource aqui é apache, mas poderia ser qualquer coisa que desejarmos)
  • Resource Properties: Aqui temos apenas package_name como propriedade, no qual damos o nome do pacote desejado. OBS: Caso não utilizemos a propriedade package_name, ele buscará por um pacote com mesmo nome do resource. No nosso caso, demos o nome apache para nosso resource, portanto ele buscaria por um pacote chamado apache e falharia, pois no CentOS este pacote não existe.
  • Actions: install (Temos apenas uma ação para esta recipe, que é justamente a de instalar o pacote, caso já não esteja instalado (idempotência))

Salve o arquivo e verifique o mesmo com os dois passos a seguir:

1- Verifique se a sintaxe ruby está correta:

1
2
3
# ruby -c exemplo.rb

Syntax OK

2- Utilize uma ferramenta do chef para verificar se a recipe está de acordo com o esperado pelo Chef:

1
2
3
4
5
6
# foodcritic exemplo.rb
Checking 1 files
x
FC011: Missing README in markdown format: ../README.md:1
FC031: Cookbook without metadata.rb file: ../metadata.rb:1
FC071: Missing LICENSE file: ../LICENSE:1

PS: Não se espante por enquanto com estes Warnings. Ele apenas está indicando que não possuímos um metadata, um readme e uma licença, pois não os criamos para este exemplo.

Verificado o código e aprovado, vamos executar esta recipe localmente. (Repare no retorno que será apresentado, onde ele verifica o tipo de resource e identifica que estamos rodando em uma máquina CentoOS, portanto utiliza por padrão o yum para instalar o pacote desejado.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# chef-client --local-mode exemplo.rb

[2018-04-01T19:18:00+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-01T19:18:00+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-01T19:18:03+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 1 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install
    - install version 2.4.6-67.el7.centos.6 of package httpd

Running handlers:
Running handlers complete
Chef Client finished, 1/1 resources updated in 16 seconds
[2018-04-01T19:18:17+00:00] WARN: No config file found or specified on command line, using command line options.

Simples, certo? O pacote httpd (Apache) foi instalado em nosso CentOS. Verificando o status do serviço httpd, veremos que o serviço não está rodando e também não está ativo.

1
2
3
4
5
6
# systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:httpd(8)
           man:apachectl(8)

Isto está correto, afinal o Chef vai deixar a máquina no estado que determinamos. O determinado foi apenas instalar o pacote httpd. Mas de nada ele serve sem estar rodando como serviço, portanto vamos editar nossa recipe exemplo.rb e incluir nela um novo resource, desta vez um resource do tipo service, ou serviço. Sim, podemos ter diversos resources em uma mesma recipe. ;]

Edite sua recipe para que ela possua o seguinte conteúdo:

1
2
3
4
5
6
7
8
package 'apache' do
        package_name 'httpd'
        action :installchef_httpd_default.png
end

service 'httpd' do
        action [:enable, :start]
end

O que adicionamos aqui?

  • Resource Type: service (Pois queremos gerenciar o serviço httpd)
  • Resource Name: httpd (Poderíamos ter utilizado qualquer nome, mas para simplificar e não precisarmos utilizar uma propriedade de nome, deixaremos o resource com o nome do serviço, httpd)
  • Actions: enable e start (Como nosso objetivo é não apenas iniciar o serviço, mas também deixá-lo habilitado para ser iniciado automaticamente após reinicialização, utilizaremos o enable e o start) –> equivalente aos comandos systemctl enable httpd e systemctl start httpd

Novamente vamos verificar nosso código via ruby e foodcritic:

1
# ruby -c exemplo.rb && foodcritic exemplo.rb

E executando nossa recipe. (Novamente, o pacote httpd já está instalado, esta parte da recipe será ignorada automaticamente pelo Chef.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# chef-client --local-mode exemplo.rb
[2018-04-01T19:32:26+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-01T19:32:26+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-01T19:32:28+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 2 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable
    - enable service service[httpd]
  * service[httpd] action start
    - start service service[httpd]

Running handlers:
Running handlers complete
Chef Client finished, 2/3 resources updated in 06 seconds
[2018-04-01T19:32:33+00:00] WARN: No config file found or specified on command line, using command line options.

Agora podemos verificar que nosso serviço httpd está de fato rodando.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# systemctl status httpd && ps aux | grep httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2018-04-01 19:32:33 UTC; 1min 17s ago
     Docs: man:httpd(8)
           man:apachectl(8)
 Main PID: 2409 (httpd)
   Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
   CGroup: /system.slice/httpd.service
           ├─2409 /usr/sbin/httpd -DFOREGROUND
           ├─2410 /usr/sbin/httpd -DFOREGROUND
           ├─2411 /usr/sbin/httpd -DFOREGROUND
           ├─2412 /usr/sbin/httpd -DFOREGROUND
           ├─2413 /usr/sbin/httpd -DFOREGROUND
           └─2414 /usr/sbin/httpd -DFOREGROUND

Apr 01 19:32:33 kalib6.test.com systemd[1]: Starting The Apache HTTP Server...
Apr 01 19:32:33 kalib6.test.com systemd[1]: Started The Apache HTTP Server.
root      2409  0.0  0.2 226040  4948 ?        Ss   19:32   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2410  0.0  0.1 226040  2872 ?        S    19:32   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2411  0.0  0.1 226040  2872 ?        S    19:32   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2412  0.0  0.1 226040  2872 ?        S    19:32   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2413  0.0  0.1 226040  2872 ?        S    19:32   0:00 /usr/sbin/httpd -DFOREGROUND
apache    2414  0.0  0.1 226040  2872 ?        S    19:32   0:00 /usr/sbin/httpd -DFOREGROUND
root      2425  0.0  0.0 112660   976 pts/0    R+   19:33   0:00 grep --color=auto httpd

Além disso, você pode testar seu novo servidor web diretamente em seu navegador. Caso esteja executando tudo em localhost, pode utilizar localhost como endereço. Caso contrário, pode utilizar o endereço ip da máquina em questão.

A página padrão do Apache não é algo que queremos, portanto vamos criar nosso próprio site (Hello World) para exemplificar melhor. Para isto editaremos novamente nossa recipe e incluiremos mais um resource, do tipo file, ou arquivo. O conteúdo de sua recipe deverá ficar assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package 'apache' do
        package_name 'httpd'
        action :install
end

service 'httpd' do
        action [:enable, :start]
end

file '/var/www/html/index.html' do
        content 'Hello World!'
        mode '0755'
        owner 'root'
        group 'apache'
end

O que adicionamos aqui?

  • Resource Type: file (Pois queremos gerenciar o nosso arquivo index.html, o qual, caso não exista, será criado)
  • Resource Name: /var/www/html/index.html (Poderíamos ter utilizado qualquer nome, mas para simplificar e não precisarmos utilizar uma propriedade de nome, deixaremos o resource com o nome do arquivo que vamos utilizar, /var/www/html/index.html)
  • Content: Hello World! (Para simplificar teremos uma simples string Hello World! como conteúdo de nosso site)
  • Mode: Permissão que desejamos atribuir ao arquivo index.html
  • Owner: Dono que deve ser atribuído ao arquivo index.html
  • Group: Grupo que deve ser atribuído ao arquivo index.html

Novamente vamos verificar nosso código via ruby e foodcritic:

1
# ruby -c exemplo.rb && foodcritic exemplo.rb

E executaremos novamente nossa recipe. (Assim como anteriormente, o Chef ignorará as instruções referentes aos resources que já se encontram no estado desejado)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# chef-client --local-mode exemplo.rb          
[2018-04-01T19:48:00+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-01T19:48:00+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-01T19:48:02+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 3 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create
    - create new file /var/www/html/index.html
    - update content in file /var/www/html/index.html from none to 7f83b1
    --- /var/www/html/index.html        2018-04-01 19:48:06.829566745 +0000
    +++ /var/www/html/.chef-index20180401-2631-164958p.html     2018-04-01 19:48:06.829566745 +0000
    @@ -1 +1,2 @@
    +Hello World!
    - change mode from '' to '0755'
    - change owner from '' to 'root'
    - change group from '' to 'apache'
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 1/4 resources updated in 06 seconds
[2018-04-01T19:48:07+00:00] WARN: No config file found or specified on command line, using command line options.

Podemos verificar que o Chef criou o nosso arquivo index.html, atribuindo o dono, grupo e permissão que indicamos:

1
2
3
# ls -lh /var/www/html/
total 4.0K
-rwxr-xr-x. 1 root apache 12 Apr  1 19:48 index.html

Também podemos voltar em nosso navegador e atualizar a página para que vejamos o nosso Hello World ao invés da página padrão do Apache.

Vale a pena?

Realizar este processo manualmente na mesma máquina CentoOS seria mais rápido do que utilizando o Chef. Vamos rever:

O que precisamos?

  • Instalar o pacote httpd
  • Habilitar e Iniciar o serviço httpd
  • Criar o arquivo index.html com o conteúdo “Hello World!”

Fazendo manualmente seria apenas uma questão de executarmos 4 comandos:

1
2
3
4
5
6
7
# yum install httpd

# systemctl enable httpd && systemctl start httpd

# echo "Hello World!" > /var/www/html/index.html

# chmod 0755 /var/www/html/index.html && chown root:apache /var/www/html/index.html

Sim, é verdade que fazendo manualmente neste caso seria MUITO mais rápido. O importante é lembrar do que falamos anteriormente: E se não for apenas 1 servidor? E se for um grupo? E se ao invés de apenas 3 resources, tiver 15? ou 40? E se alguém modificar algo em algum dos resources? Como você saberá qual foi? Vai verificar todos um a um para identificar o que precisa ser corrigido?

Vantagens:

Imagine que algum membro de sua equipe alterou a permissão do arquivo index.html sem lhe avisar, por exemplo ele foi lá e…

1
# chmod 0666 /var/www/html/index.html

Lembrando que em nossa recipe exemplo.rb, definimos a permissão 0755. Neste caso, sempre que executarmos a recipe, o Chef irá verificar todos os resources e corrigir o que quer que tenha sido alterado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# chef-client --local-mode exemplo.rb
[2018-04-01T20:04:08+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-01T20:04:08+00:00] WARN: No cookbooks directory found at or above current directory.  Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-01T20:04:10+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 3 resources
Recipe: @recipe_files::/root/exemplo.rb
  * yum_package[apache] action install (up to date)
  * service[httpd] action enable (up to date)
  * service[httpd] action start (up to date)
  * file[/var/www/html/index.html] action create
    - change mode from '0666' to '0755'
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 1/4 resources updated in 06 seconds
[2018-04-01T20:04:14+00:00] WARN: No config file found or specified on command line, using command line options.

Repare na linha – change mode from ‘0666’ to ‘0755’. O Chef acabou de corrigir automaticamente sem que nós tenhamos de vasculhar cada componente e arquivo em nosso servidor para saber o que foi alterado ou o que está diferente do estado desejado. Novamente, aqui tratamos de um servidor único, com apenas um arquivo. Imagine ter que varrer manualmente diversos servidores e diversos arquivos e diretórios?

Neste exemplo, provavelmente o site poderia continuar funcionando, pois foi alterada apenas a permissão de um único arquivo, certo? Mas imagine que sem querer ele acabou parando o serviço httpd, ou até desinstalou o mesmo? Em uma instalação padrão com Chef Server, existem agendamentos que fazem com que o Chef execute as recipes a cada X minutos, portanto o serviço seria inicializado novamente automaticamente, ou mesmo instalado caso necessário.

É fácil imaginar diversos cenários nos quais seria útil ter a sua infraestrutura em formato de código.

Imagine uma catástrofe em que seu servidor simplesmente parou de funcionar e você precisará criar outro. Novamente você teria que executar aqueles comandos. Se desde o início tivesse utilizado Chef, ou outra ferramenta de Gerenciamento de Configuração, você poderia ter a sua recipe armazenada em um repositório Git, por exemplo, conforme mencionado no início deste post, e bastaria apenas executar o seu chef-client para instalar os pacotes necessários, habilitar e inicializar serviços, criar arquivos, etc.

A imaginação é o seu limite. ;]

Em posts futuros pretendo explorar mais a fundo o Chef, bem como outras ferramentas.

Happy Hacking!