Rodando PuppetMaster com Nginx e Unicorn no Ubuntu

1. Acelerando e escalando master com Unicorn e Nginx

Neste post pretendo demonstrar como instalar e rodar o PuppetMaster utilizando o UNICORN como servidor de aplicação e NGINX como proxy reverso.

Se deseja fazer esse procedimento no CentOS, acesse esse link.

O UNICORN é um servidor web especializado em apps ruby, escrito em ruby e focado em performance, muito mais eficiente e rápido do que o webrick, e também com melhor performance se compararmos com apache + passenger.

2. Cenário

Neste cenário utilizei apenas uma VM para instalar e validar o funcionamento do puppetmaster com unicorn.

3. Procedimento

Abaixo segue o procedimento passo a passo para configuração deste cenário.

3.1 Configurando hostname

Ajuste o nome do seu servidor nos seguintes arquivos para puppet.hacklab

# echo puppet.hacklab > /etc/hostname
# echo 127.0.0.1 localhost puppet.hacklab puppet > /etc/hosts
# hostname puppet.hacklab

Faça logout e login e depois rode o comando abaixo

# hostname

Ele precisa retornar o nome

puppet.hacklab

3.2 Instalando repositório

Faça o download do pacote release da puppetlabs

wget http://apt.puppetlabs.com/puppetlabs-release-precise.deb

Instalando o repositório da puppetlabs

dpkg -i puppetlabs-release-precise.deb

3.3 Instalando puppetmaster

Instale o puppetmaster

aptitude install puppetmaster

Desative o daemon puppetmaster no boot pois vamos usar o unicorn

update-rc.d -f puppetmaster disable

Desligue o daemon puppetmaster para que a porta 8140 seja liberada para o nginx

/etc/init.d/puppetmaster stop

Comente as linhas abaixo no /etc/puppet/puppet.conf (importante!)

ssl_client_header = SSL_CLIENT_S_DN
ssl_client_verify_header = SSL_CLIENT_VERIFY

3.4 Instalando Unicorn

Instale as dependências necessárias

aptitude install make gcc ruby-dev rubygems

Instale o unicorn e rack utilizando gem (não tem pacotes deb)

gem install unicorn rack

Crie o arquivo /etc/puppet/config.ru como seguinte conteúdo

# a config.ru, for use with every rack-compatible webserver.
# SSL needs to be handled outside this, though.

# if puppet is not in your RUBYLIB:
# $LOAD_PATH.unshift('/opt/puppet/lib')

$0 = "master"

# if you want debugging:
# ARGV << "--debug"

ARGV << "--rack"

# Rack applications typically don't start as root.  Set --confdir and --vardir
# to prevent reading configuration from ~puppet/.puppet/puppet.conf and writing
# to ~puppet/.puppet
ARGV << "--confdir" << "/etc/puppet"
ARGV << "--vardir"  << "/var/lib/puppet"

# NOTE: it's unfortunate that we have to use the "CommandLine" class
#  here to launch the app, but it contains some initialization logic
#  (such as triggering the parsing of the config file) that is very
#  important.  We should do something less nasty here when we've
#  gotten our API and settings initialization logic cleaned up.
#
# Also note that the "$0 = master" line up near the top here is
#  the magic that allows the CommandLine class to know that it's
#  supposed to be running master.
#
# --cprice 2012-05-22

require 'puppet/util/command_line'
# we're usually running inside a Rack::Builder.new {} block,
# therefore we need to call run *here*.
run Puppet::Util::CommandLine.new.execute

Caso as configurações do puppet estejam em um diretório diferente do padrão, ajuste a linha confdir, a mesma coisa para o diretório em que ficam os arquivos variáveis

ARGV << "--confdir" << "/etc/puppet"
ARGV << "--vardir"  << "/var/lib/puppet"

Crie o arquivo /etc/puppet/unicorn.conf com o seguinte conteúdo

 worker_processes 8
    working_directory "/etc/puppet"
    listen '/var/run/puppet/puppetmaster_unicorn.sock', :backlog => 512
    timeout 120
    pid "/var/run/puppet/puppetmaster_unicorn.pid"

    preload_app true
    if GC.respond_to?(:copy_on_write_friendly=)
      GC.copy_on_write_friendly = true
    end

    before_fork do |server, worker|
      old_pid = "#{server.config[:pid]}.oldbin"
      if File.exists?(old_pid) && server.pid != old_pid
        begin
          Process.kill("QUIT", File.read(old_pid).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
          # someone else did our job for us
        end
      end
    end

Caso as configurações do puppet estejam em diretório diferente do padrão, ajuste a diretiva working_directory.

working_directory "/etc/puppet"

Inicie o serviço para testar o unicorn

root@ubuntu:/etc/puppet# unicorn -c unicorn.conf

Veja a saída esperada

I, [2014-03-22T00:09:14.089524 #2415]  INFO -- : Refreshing Gem list
I, [2014-03-22T00:09:15.402416 #2415]  INFO -- : listening on addr=/var/run/puppet/puppetmaster_unicorn.sock fd=8
I, [2014-03-22T00:09:15.432006 #2438]  INFO -- : worker=0 spawned pid=2438
I, [2014-03-22T00:09:15.444172 #2438]  INFO -- : worker=0 ready
I, [2014-03-22T00:09:15.462684 #2439]  INFO -- : worker=1 spawned pid=2439
I, [2014-03-22T00:09:15.476063 #2439]  INFO -- : worker=1 ready
I, [2014-03-22T00:09:15.494481 #2440]  INFO -- : worker=2 spawned pid=2440
I, [2014-03-22T00:09:15.519071 #2440]  INFO -- : worker=2 ready
I, [2014-03-22T00:09:15.566899 #2441]  INFO -- : worker=3 spawned pid=2441
I, [2014-03-22T00:09:15.579692 #2441]  INFO -- : worker=3 ready
I, [2014-03-22T00:09:15.600440 #2442]  INFO -- : worker=4 spawned pid=2442
I, [2014-03-22T00:09:15.652091 #2442]  INFO -- : worker=4 ready
I, [2014-03-22T00:09:15.663205 #2443]  INFO -- : worker=5 spawned pid=2443
I, [2014-03-22T00:09:15.676469 #2443]  INFO -- : worker=5 ready
I, [2014-03-22T00:09:15.724854 #2444]  INFO -- : worker=6 spawned pid=2444
I, [2014-03-22T00:09:15.735700 #2415]  INFO -- : master process ready
I, [2014-03-22T00:09:15.751096 #2445]  INFO -- : worker=7 spawned pid=2445
I, [2014-03-22T00:09:15.758621 #2444]  INFO -- : worker=6 ready
I, [2014-03-22T00:09:15.760709 #2445]  INFO -- : worker=7 ready

Se houver uma saída similar é sinal de que está fucionando como esperado, dê um CTRL+C para interromper o processo.

^C

Crie o arquivo /etc/init/unicorn com o conteúdo abaixo


# When to start the service
start on runlevel [2345]

# When to stop the service
stop on runlevel [016]

# Automatically restart process if crashed
respawn
respawn limit 5 15

# Upstart will expect the process executed to call fork(2) exactly twice.
expect daemon

exec /usr/local/bin/unicorn -c /etc/puppet/unicorn.conf -D

Inicie o serviço

 start unicorn

Verifique se o unicorn está realmente rodando

ps aux|grep unicorn

A saída será similar a esta abaixo

puppet    1889  0.5  9.1 119412 45380 ?        S    20:05   0:01 unicorn master -D -c /etc/puppet/unicorn.conf
puppet    1896  0.0  8.7 119376 43416 ?        S    20:05   0:00 unicorn worker[0] -D -c /etc/puppet/unicorn.conf
puppet    1897  0.0  8.7 119380 43416 ?        S    20:05   0:00 unicorn worker[1] -D -c /etc/puppet/unicorn.conf
puppet    1898  0.0  8.7 119384 43416 ?        S    20:05   0:00 unicorn worker[2] -D -c /etc/puppet/unicorn.conf
puppet    1899  0.0  8.7 119388 43416 ?        S    20:05   0:00 unicorn worker[3] -D -c /etc/puppet/unicorn.conf
puppet    1900  0.0  8.7 119392 43416 ?        S    20:05   0:00 unicorn worker[4] -D -c /etc/puppet/unicorn.conf
puppet    1901  0.0  8.7 119396 43416 ?        S    20:05   0:00 unicorn worker[5] -D -c /etc/puppet/unicorn.conf
puppet    1902  0.0  8.7 119400 43416 ?        S    20:05   0:00 unicorn worker[6] -D -c /etc/puppet/unicorn.conf
puppet    1903  0.0  8.7 119404 43416 ?        S    20:05   0:00 unicorn worker[7] -D -c /etc/puppet/unicorn.conf

3.5 Instalando o Nginx

Instale o nginx

aptitude install nginx

Ative o nginx no boot

update-rc.d -f nginx defaults

Crie o arquivo /etc/nginx/sites-available/puppetmaster com o conteúdo abaixo

    upstream puppetmaster_unicorn {
        server unix:/var/run/puppet/puppetmaster_unicorn.sock fail_timeout=0;
    }

    server {
        listen 8140;

        ssl on;
        ssl_session_timeout 5m;
        ssl_certificate /var/lib/puppet/ssl/certs/puppet.hacklab.pem;
        ssl_certificate_key /var/lib/puppet/ssl/private_keys/puppet.hacklab.pem;
        ssl_client_certificate /var/lib/puppet/ssl/ca/ca_crt.pem;
        ssl_ciphers SSLv2:-LOW:-EXPORT:RC4+RSA;
        ssl_verify_client optional;

        root /usr/share/empty;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_set_header X-Client-DN $ssl_client_s_dn;
        proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
        proxy_read_timeout 120;

        location / {
            proxy_pass http://puppetmaster_unicorn;
            proxy_redirect off;
        }
    }

Lembre-se de ajustar o nome do certificado de acordo com seu FQDN, crie o link simbólico em seguida

ln -s /etc/nginx/sites-available/puppetmaster /etc/nginx/sites-enabled/puppetmaster

Reinicie o nginx

/etc/init.d/nginx restart

Verfique se a porta 8140 está em modo listen

root@puppet:/etc/nginx/sites-enabled# netstat -ntpl|grep 8140

Acompanhe a saída

tcp        0      0 0.0.0.0:8140            0.0.0.0:*               LISTEN      9960/nginx

3.7 Testando o Puppet

Crie o arquivo /etc/puppet/manifests/site.pp com o conteúdo abaixo

node "puppet.hacklab" {

        package { 'htop':
                ensure => present,
        }

}

Agora vamos testar se o puppet está funcionando.

root@puppet:/etc/puppet# puppet agent -t

Acompanhe a saída

Info: Retrieving plugin
Info: Caching catalog for puppet.hacklab
Info: Applying configuration version '1395768087'
Notice: /Stage[main]/Main/Node[puppet.hacklab]/Package[htop]/ensure: ensure changed 'purged' to 'present'
Notice: Finished catalog run in 11.98 seconds

Veja o log do Nginx

tail -f /var/log/nginx/access.log

Acompanhe o nginx atuando como reverse proxy do unicorn

172.16.184.153 - - [25/Mar/2014:17:19:54 +0000] "POST /production/catalog/puppet.hacklab HTTP/1.1" 400 121 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:19:55 +0000] "PUT /production/report/puppet.hacklab HTTP/1.1" 200 9 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:20:45 +0000] "GET /production/node/puppet.hacklab? HTTP/1.1" 200 4115 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:20:45 +0000] "GET /production/file_metadatas/plugins?ignore=.svn&ignore=CVS&ignore=.git&checksum_type=md5&links=manage&recurse=true HTTP/1.1" 200 283 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:20:50 +0000] "POST /production/catalog/puppet.hacklab HTTP/1.1" 400 164 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:20:50 +0000] "PUT /production/report/puppet.hacklab HTTP/1.1" 200 9 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:21:23 +0000] "GET /production/node/puppet.hacklab? HTTP/1.1" 200 4115 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:21:23 +0000] "GET /production/file_metadatas/plugins?ignore=.svn&ignore=CVS&ignore=.git&checksum_type=md5&links=manage&recurse=true HTTP/1.1" 200 283 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:21:28 +0000] "POST /production/catalog/puppet.hacklab HTTP/1.1" 200 1022 "-" "-"
172.16.184.153 - - [25/Mar/2014:17:21:41 +0000] "PUT /production/report/puppet.hacklab HTTP/1.1" 200 9 "-" "-"

Configuração aplicada, isto significa que o puppetmaster está rodando com Unicorn e Nginx, encaixe perfeito.

4. Amarrando as pontas

É um processo relativamente simples e bastante rápido, utilizamos apenas pacotes oficiais da puppetlabs e ubuntu, e dois gems. Se quiser pode integrar o puppetdb no mesmo cenário. Com esse setup o puppetmaster fica rápido e escalável, recomendo.

5. Referências

Projects

Blogs