Puppet-Master unter CentOS

Ziel dieser Anleitung ist es, ein skalierendes Puppet Setup auf einem CentOS 6.5 Host bereitzustellen. Vorinstalliert ist ausschließlich ein sshd.

Puppet ist in Ruby geschrieben und unterstützt mittlerweile Ruby2. Hier gibt es signifikante Performanceoptimierungen im Vergleich zu Ruby 1.8.7. Leider ist dies die Standardversion unter CentOS, weshalb wir Ruby 2 über RVM nachinstallieren:

curl -L get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm requirements
rvm install 2.1.1
rvm use 2.1.1 --default
rvm rubygems current

Außerdem ist es empfehlenswert auf einen aktuelleren Kernel zu wechseln (z.B. kernel-lt), diese bieten große Performanceoptimierungen:
Installation des ELRepo + Kernel-LT

Zusätzlich wird das EPEL Repository benötigt für aktuelle Tools wie z.B. htop:
Installation von EPEL

Der Puppet-Master ist eine Ruby App welche einen Webserver benötigt da es mit den Clients über eine Pseudo-YAML/HTTP API spricht. In großen Setups bietet sich nginx an da dieser effizienter als Apache arbeitet. Die nginx Version im CentOS Repo ist leider sehr alt, glücklicherweise bieten die nginx Entwickler ein eigenes Repo an. Eine Anleitung zum einrichten findet sich hier.

Danach kann nginx ganz normal installiert, gestartet und für den Autostart markiert werden:

yum install nginx
service nginx configtest
service nginx start
chkconfig nginx on

Für Puppet muss noch ein vHost unter /etc/nginx/conf.d/puppet.conf angelegt werden:

server {
  listen                     8140 ssl;
  server_name                puppet puppet.example.org;

  access_log                 /var/log/nginx/puppet_access.log;
  error_log                  /var/log/nginx/puppet_error.log;

  root                       /usr/share/puppet/rack/puppetmasterd/public;
  # accept huge puppet reports
  client_max_body_size 5M;
  ssl_certificate            /var/lib/puppet/ssl/certs/puppet.example.com.pem;
  ssl_certificate_key        /var/lib/puppet/ssl/private_keys/puppet.example.com.pem;
  ssl_crl                    /var/lib/puppet/ssl/ca/ca_crl.pem;
  ssl_client_certificate     /var/lib/puppet/ssl/certs/ca.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256: [BR] +AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED: [BR] !ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA;
  add_header Strict - Transport - Security max - age =2592000;
  ssl_prefer_server_ciphers  on;
  ssl_verify_client          optional;
  ssl_verify_depth           1;
  ssl_session_cache          shared:SSL:128m;
  ssl_session_timeout        5m;
  proxy_redirect http://$host/ https://$host/;
  proxy_redirect      off;
  proxy_set_header    X-Real-IP        $remote_addr;
  proxy_set_header    X-Forwarded-For  $proxy_add_x_forwarded_for;
  proxy_set_header    X-Scheme         $scheme;
  proxy_set_header    X-Client-Verify  $ssl_client_verify;
  proxy_set_header    X-Client-DN      $ssl_client_s_dn;
  proxy_set_header    X-SSL-Subject    $ssl_client_s_dn;
  proxy_set_header    X-SSL-Issuer     $ssl_client_i_dn;
  proxy_read_timeout  65;
  proxy_set_header X-Forwarded-Protocol https;
  proxy_set_header X-Forwarded-SSL on;
 }
 upstream backend  {
  server unix:/usr/share/puppet/rack/puppetmasterd/tmp/production.socket;
} 
}

Nginx fungiert nach außen als Proxy, der Puppet Server ist in Ruby geschrieben, dieser Ruby Code wird von Puma ausgeführt und an nginx weitergereicht. Puma wird über gem installiert, dem Ruby Libs/Apps Paket Manager. Dieser hat noch diverse Systemabängigkeiten die im Vorfeld installiert werden müssen, im Anschluss erfolgt die eigentliche Installation von puma. Als Schnittstelle zwischen unserem Ruby Webserver Puma und der eigentlichen Ruby-App läuft Rack, dies wird ebenfalls über gem installiert:

yum install ruby-devel gcc openssl-devel
gem install puma rack

Puppetlabs stellt einen yum Mirror mit aktuellen Packages für CentOS 6 bereit. Der Mirror wird wie folgt eingerichtet:

yum install http://yum.puppetlabs.com/el/6/products/x86_64/puppetlabs-release-6-7.noarch.rpm

Installation von Puppet:

yum update
yum install puppet-server

Alternativer Weg:
Puppet kann man auch über gem installieren. Ob man yum oder gem bevorzugt ist Geschmackssache, in meinem Fall ist es gem:

gem install puppet

In beiden Fällen ist es danach noch nötig einige Ordner anzulegen und passende Rechte zu setzen:
mkdir -p /usr/share/puppet/rack/puppetmasterd
mkdir /usr/share/puppet/rack/puppetmasterd/public /usr/share/puppet/rack/puppetmasterd/tmp /usr/share/puppet/rack/puppetmasterd/log
cp /usr/share/puppet/ext/rack/files/config.ru /usr/share/puppet/rack/puppetmasterd/
chown puppet:puppet /usr/share/puppet/rack/puppetmasterd/*[/bash]

In der /etc/puppet/puppet.conf muss noch folgender Part ergänzt werden:

[agent]
  server = puppet.example.org
[master]
  certname = puppet.example.org

Puma lässt sich nun schon auf der Bash starten:

puma /usr/share/puppet/rack/puppetmasterd/config.ru
Puma starting in single mode...
* Version 2.7.1, codename: Earl of Sandwich Partition
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:9292
Use Ctrl-C to stop

Wir möchten Puma aber noch mehrere Parameter übergeben. Z.B. ein Pfad zu einer Logdatei, zum passenden Unix Socket auf dem gelauscht werden soll oder die zugehörige pid Datei. Dazu wird die Datei /usr/share/puppet/rack/puppetmasterd/puma.rb erstellt mit folgendem Inhalt:

#!/usr/bin/env puma

application_path = '/usr/share/puppet/rack/puppetmasterd/'
railsenv = 'production'
directory application_path
environment railsenv
daemonize true
pidfile "#{application_path}tmp/puma-#{railsenv}.pid"
state_path "#{application_path}tmp/puma-#{railsenv}.state"
stdout_redirect
"#{application_path}/log/puma-#{railsenv}.stdout.log"
"#{application_path}/log/puma-#{railsenv}.stderr.log"
threads 0, 16
bind "unix://#{application_path}tmp/#{railsenv}.socket"

Puma startet sich nun wie folgt:

RAILS_ENV=production puma -C /usr/share/puppet/rack/puppetmasterd/puma.rb

Über ein Gemset kann man definieren welche Gems alle benötigt werden für eine Ruby App. Über dieses Gemfile kann man gezielt alle passenden Abhängigkeiten automatisch installieren und updaten. Das Gemfile wird als /usr/share/puppet/rack/puppetmasterd/Gemfile mit folgendem Inhalt erstellt:

gem 'puma'
gem 'rack'
gem 'puppet'

Puma muss nun leicht abgeändert gestartet werden und liefert folgenden Output:

RAILS_ENV=production bundle exec puma -C /usr/share/puppet/rack/puppetmasterd/puma.rb
Puma starting in single mode...
* Version 2.7.1, codename: Earl of Sandwich Partition
* Min threads: 0, max threads: 16
* Environment: production

Warum Puma:
In den meisten Installationsanleitungen wird Passenger als Ruby Webserver empfohlen. Dieser bietet von Haus aus ein passendes Apache Modul an. Nginx benötigt ebenfalls ein Modul, leider können hier keine Module zur Laufzeit eingebunden werden sondern nur bei der initialen Kompilierung. Aufgrund der Wartbarkeit möchte ich auf selbst kompilierte Software verzichten. Puma ist ein relativ junger Ruby Webserver welcher laut einigen Benchmarks mit Passenger konkurieren kann hinsichtlich der Performance. Puma kann zwar (im Gegensatz zu Passenger) weder node.js noch Python ansteuern sondern nur Ruby, aber dies ist in unserem Fall ausreichend.

Installation der Puppetdb:
In der PuppetDB können Facts und Kataloge der einzelnen Nodes gespeichert werden, dies wird für Funktionen wie exported resources benötigt. Die PuppetDB ist in Java geschrieben und schreibt Ihre Daten in ein Postgres Backend. CentOS 6.5 bietet nur Postgres 8.4, aus Performancegründen installieren wir die aktuelle 9.3 Version direkt von den Postgres Entwicklern:

Zuerst müssen wir in der Datei /etc/yum.repos.d/CentOS-Base.repo in den Abschnitten [base] und [updates] den Eintrag exclude=postgresql* ergänzen. Danach fügen wir das neue Repo hinzu, installieren die benötigten Pakete, initialisieren Postgres und setzen den Dienst auf autostart:

yum install http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm
yum install postgresql93-server postgresql93-contrib
service postgresql-9.3 initdb
chkconfig postgresql-9.3 on

Die PuppetDB wird über ein Puppet Modul installiert, dieses wird zuerst von Puppet Forge heruntergeladen:

puppet module install puppetlabs/puppetdb

Das Modul muss noch angepasst werden, ansonsten installiert es Postgres 8.4 und nutzt nicht die schon installierte Version 9.3. Dies geschieht in der Datei /etc/puppet/modules/postgresql/manifests/params.pp. In den Zeilen 25-31 werden die zu installierenden Pakete aufgelistet. Die neuen Versionen heisen nicht mehr nur noch ‘postgres’ sondern nun ‘postgresql93’.

Die PuppetDB Klasse kann man nun dem Node zuweisen in der /etc/puppet/manifests/site.pp:
node /puppet.example.org/ {
include puppetdb
include puppetdb::master::config
}

Die Vorbereitungen sind abgeschlossen, nun kann ein puppet Run erfolgen welcher die DB einrichtet:

puppet agent -t

Warum ein Loadbalancer/Proxy vor dem eigentlichen Webserver?
Nginx ist ein leichtgewichtiger Webserver mit vielen unbekannten Talenten. Zum einen kann er auch als Webfirewall dienen und bösartige Anfragen herausfiltern um somit Attacken auf Puma/Rack/Puppet-Master zu unterbinden, zum anderen kann er SSL terminieren und mehrere Backends ansprechen. Dies ist notwendig um ein flexibles Setup zu bauen das man problemlos um mehrere Puppet-Master Nodes erweitern und verkleinern kann.

Abschliesende Funktionalitätstests:
Man kann sich nun auf einem Client Puppet installieren und testweise eine Verbindung zu unserem neuen Setup aufbauen. Hier eine beispielhafte Installationsanleitung für einen Puppet Client unter Debian Wheezy. Leider ist die Version in dem Repo zu alt weshalb ich zuerst den APT-Repo von Puppetlabs hinzufüge:

 wget https://apt.puppetlabs.com/puppetlabs-release-wheezy.deb
dpkg -i puppetlabs-release-wheezy.deb
aptitude update
aptitude -y safe-upgrade
aptitude install puppet

Nun kann der erste Puppetrun gestartet werden:

puppet agent --server puppet.example.com --onetime --no-daemonize --verbose --noop
Info: Creating a new SSL key for meine-tolle-testkiste.example.com
Info: Caching certificate for ca
Info: csr_attributes file loading from /etc/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for meine-tolle-testkiste.example.com
Info: Certificate Request fingerprint (SHA256): D9:13:F7:01:FF:F8:97:97:40:95:95:55:21:FA:D2:02:EF:05:9D:14:E5:45:3C:CD:61:4E:44:AC:B8:CD:19:05
Info: Caching certificate for ca
Exiting; no certificate found and waitforcert is disabled

Auf unserem Puppet-Master liegt nun ein Clientzertifikat das wir signieren müssen. Danach kann der Client SSL-verschlüsselt mit dem Master kommunizieren:

puppet cert sign meine-tolle-testkiste.example.com
Notice: Signed certificate request for meine-tolle-testkiste.example.com
Notice: Removing file Puppet::SSL::CertificateRequest meine-tolle-testkiste.example.com at '/var/lib/puppet/ssl/ca/requests/meine-tolle-testkiste.example.com.pem'

Ein erneuter Puppet run auf dem Client ist nun erfolgreich:

puppet agent --server puppet.rackmonkey.de --onetime --no-daemonize --verbose --noop
Info: Retrieving plugin
Info: Caching catalog for host02.bastelfreak.org
Info: Applying configuration version '1387582674'
Notice: Finished catalog run in 0.01 seconds

Für ein ordentliches Life-Cycle Management sowie als Webfrontend für Puppet bietet sich Foreman an. Zur dessen Installation wird zuerst das RPM Repository hinzugefügt, dann benötigte Gems installiert und dann der eigentliche Foreman:

yum install http://yum.theforeman.org/releases/latest/el6/x86_64/foreman-release.rpm
yum update
yum upgrade
gem install highline
gem install kafo
yum install foreman-installer

Nützliche Links:
Wie implementiert man sichere Crypto
Rails Server Throwdown
Nginx+Passenger für den Puppet-Master
Anleitung von Puppetlabs für Apache+Passenger/Rack
Puma+Nginx 1
Puma+Nginx 2
Sicherer SSL Cipher Suite inklusive Perfect Forward Secrecy
Informationen über PFS (Perfect Forward Secrecy)
Installation einer PuppetDB unter CentOS 6.4

This entry was posted in General, Linux, Nerd Stuff, Puppet. Bookmark the permalink.

1 Response to Puppet-Master unter CentOS

  1. tholie says:

    Hallo,

    obwohl das HOWTO super verständlich geschrieben ist, ich habe hier ein Problem.
    Beim: puma /usr/share/puppet/rack/puppetmasterd/config.ru erhalte ich folgenden Fehler:

    Puma starting in single mode…
    * Version 2.7.1, codename: Earl of Sandwich Partition
    * Min threads: 0, max threads: 16
    * Environment: development
    ! Unable to load application
    /usr/local/rvm/rubies/ruby-2.0.0-p353/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require’: cannot load such file — puppet/util/command_line (LoadError)
    from /usr/local/rvm/rubies/ruby-2.0.0-p353/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require’
    from /usr/share/puppet/rack/puppetmasterd/config.ru:32:in `block in ‘
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/rack-1.5.2/lib/rack/builder.rb:55:in `instance_eval’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/rack-1.5.2/lib/rack/builder.rb:55:in `initialize’
    from /usr/share/puppet/rack/puppetmasterd/config.ru:in `new’
    from /usr/share/puppet/rack/puppetmasterd/config.ru:in `’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/rack-1.5.2/lib/rack/builder.rb:49:in `eval’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/rack-1.5.2/lib/rack/builder.rb:49:in `new_from_string’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/rack-1.5.2/lib/rack/builder.rb:40:in `parse_file’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/puma-2.7.1/lib/puma/configuration.rb:93:in `app’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/puma-2.7.1/lib/puma/runner.rb:105:in `load_and_bind’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/puma-2.7.1/lib/puma/single.rb:73:in `run’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/puma-2.7.1/lib/puma/cli.rb:442:in `run’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/gems/puma-2.7.1/bin/puma:10:in `’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/bin/puma:23:in `load’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/bin/puma:23:in `’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/bin/ruby_executable_hooks:15:in `eval’
    from /usr/local/rvm/gems/ruby-2.0.0-p353/bin/ruby_executable_hooks:15:in `’

    Das System ist ein frisch installiertes CentOS 6.5 minimal mit sshd. Weiß hier jemand Rat?

    Gruß tholie

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.