Clustershell und Foreman-API

i-love-apisForeman bietet die Möglichkeit verschiedene Informationen über die Hosts einzusehen. Dazu gehören der Status, das Betriebssystem, Ressourcen etc. Möchte man nun, auf mehreren Hosts gleichzeitig ein Kommando absetzen, kann man sich auf jedem einzelnen einloggen oder eine Clustershell aufbauen.
Hierfür gibt es verschiedene Tools die dies erlauben. Eine Unbequemlichkeit die hier jedoch schnell aufkommt, ist das kopieren und einfügen der Hostnamen in die Commandline. Aus diesem Grund, habe ich etwas Zeit investiert und ein Ruby Script geschrieben, das es mir ermöglicht, mit festgelegten Filtern nach speziellen Listen von Hostnamen zu suchen und diese als eine einzige Ausgabe zu speichern. Ich habe für das erzeugen von Clustershells “csshX” im Einsatz, welches ich auch direkt mit eingebunden habe.

Das get_hosts Script gibt es als GIST.

In diesem Script wird zunächst eine “config.yml” geladen, in der die Foreman-URL und der Nutzername definiert sind. Eine Passwortabfrage erfolgt in diesem Script direkt auf der Commandline. Anschließend wird die Ausgabe der Foreman-API nach dem Auflisten aller Hostinformationen in JSON geparst und alle verfügbaren Parameter für die Hosts in das entsprechende Array gespeichert. Mit dem Parameter “-s / –server” gibt man einen String an, nachdem speziell gesucht werden soll. Diese Ausgabe wird zusätzlich mit angehängt.

Gefiltert wird nach:
1) Reports enabled
2) OS Ubuntu 14.04 / Debian 8
3) Kein Match auf net-* oder netways.de (Als Beispiel)

Von den selektierten Hosts werden die Hostnamen in einer Commandline-Ausgabe mit einem Leerzeichen getrennt ausgegeben. Verschiedene werden sich eventuell fragen: “Wofür brauche ich das? Wieso sollte ich so ein Script verwenden?”
Die Antwort ist einfach: Bequemlichkeit und live Übersicht, was gerade passiert. Die Suchparameter lassen sich sehr leicht anpassen und die Ausgabe des Scriptes wird etwas an Zeit der administrativen Aufgaben sparen, vorallem dann, wenn man mehr als nur 2 oder 3 Server mit Puppet bespielen lassen möchte.

user@computer ~/Documents/ruby/foreman $ ruby script.rb
Enter password:
[ ] Trying to establish a connection...
[OK] Password correct
[OK] Connection established
[ ] Collecting data...
[OK] Data collected
[RESULTS]
Ubuntu
csshX --login root test1.test.de test2.test.de test34.test.de test19.test.de mail.test.de icinga-001.test.de
Debian
csshX --login root icinga-002.test.de db-003.test.de db-021.test.de
Finished succesfully

Wie bereits erwähnt, ist hierfür noch eine “config.yml” Datei nötig, die gewünschte Parameter enthält. In diesem Fall die URL und den usernamen. Aber auch ein Gemfile, das sich in Ruby um bestimmte Versionen von Gems kümmert. (Mit einem “bundle install” können diese installiert werden)

Die config.yml und das Gemfile gibt es ebenfalls als GIST.

Eingebaute “rescue Execptions” im Script selbst, geben entsprechende Rückmeldung, sollte der Login oder eine der auszuführenden Verarbeitungsschritte fehlschlagen und brechen den Vorgang an dieser Stelle ab.

Marius Gebert

Autor:

Marius ist seit September 2013 bei uns beschäftigt. Er hat im Sommer 2016 seine Ausbildung zum Fachinformatiker für Systemintegration absolviert und kümmert sich nun um den Support unserer Hostingkunden. Seine besonderen Themengebiete erstrecken sich vom Elastic-Stack bis hin zu Puppet. Auch an unserem Lunchshop ist er ständig zu Gange und versorgt die ganze Firma mit kulinarischen Köstlichkeiten. Seine Freizeit verbringt Marius gern an der frischen Luft und wandert dabei durch die fränkische Schweiz

May I introduce the Rubocop

When you are into developing Ruby code, or even Ruby near stuff like Puppet modules, or Chef cookbooks, there is a nice tool you should have a look at.

The RuboCop can help you writing better Ruby code, it certainly did it for me.

In short words, RubyCop is a code analyzer that checks Ruby code against common style guidelines and tries to detect a lot of mistakes and errors that you might write into your code.

There are a lot of configuration options, and even an auto-correct functionality, that updates your code.

Simple usage

Either install the gem, or add it to your Gemfile:

gem 'rubocop', require: false

You can just run it without configuration, and it will look for all Ruby files in your work directory.

$ rubocop
Inspecting 19 files
....C............CC

Offenses:

lib/test/cli.rb:3:3: C: Missing top-level class documentation comment.
 class CLI
 ^^^^^
lib/test/cli.rb:36:1: C: Extra empty line detected at method body beginning.
lib/test/cli.rb:41:1: C: Extra empty line detected at block body beginning.
lib/test/cli.rb:45:4: C: Final newline missing.
end
 
bin/test:12:1: C: Missing space after #.
#api.login('username', 'Passw0rd') unless api.logged_in?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bin/test:15:3: C: Missing space after #.
 #puts response.code
 ^^^^^^^^^^^^^^^^^^^
bin/test:19:1: C: Missing space after #.
#session.save(file)
^^^^^^^^^^^^^^^^^^^

18 files inspected, 7 offenses detected

You can add it as a rake job to your Rakefile:

require 'rubocop/rake_task'
RuboCop::RakeTask.new

task default: [:spec, :rubocop]

And run the test via your rake tests:

$ rake
$ rake rubocop

Configuration galore

There are a lot of options to modify the behavior and expectations of RuboCop.

Here is a short example I used with recent Puppet module.

require: rubocop-rspec
AllCops:
  TargetRubyVersion: 1.9
  Include:
    - ./**/*.rb
  Exclude:
    - vendor/**/*
    - .vendor/**/*
    - pkg/**/*
    - spec/fixtures/**/*

# We don't use rspec in this way
RSpec/DescribeClass:
  Enabled: False

RSpec/ImplicitExpect:
  Enabled: False

# Example length is not necessarily an indicator of code quality
RSpec/ExampleLength:
  Enabled: False

RSpec/NamedSubject:
  Enabled: False

Where to go next

Markus Frosch

Autor: Markus Frosch

Markus arbeitet bei NETWAYS als Principal Consultant und unterstützt Kunden bei der Implementierung von Nagios, Icinga und anderen Open Source Systems Management Tools. Neben seiner beruflichen Tätigkeit ist Markus aktiver Mitarbeiter im Debian Projekt.

Puppet Hash Injection

forge-logoFolgende Ausgangssituation, wir wollen eine Konfigurationsdatei zeilenweise mit beliebigen Optionen und den zugehörigen Werten verwalten. Hierzu benutzen wir eine Defined Resource, um jeweils einen Eintrag zu konfiguration:

define config::setting(
  $option = $title,
  $value,
) {
  file_line { "config::setting::${option}":
    line => "${option} = ${value}",
    ...
}

Nun möchten wir jedoch aus einer Klasse heraus, einen einfachen Hash zur Deklaration verwenden und nicht einen Hash of Hashes.

class { 'config':
  options => { 'opt1' => 'alpha', 'opt2' => 'beta' },
}

Das Problem ist nun, dass wir aus options einen Hash der folgenden Form machen müssen, um mit create_resources, opt1 und opt2 anlegen zu können.

{ 'opt1' => { 'value' => 'alpha' },
  'opt2' => { 'value' => 'beta' },
}

Ruby bietet für Hashes eine Methode inject, die wir innerhalb eines inline_templates anwenden. Die inline_template Funktion liefert jedoch nur Strings zurück und keinen Hash. Deshalb geben wir den String in YAML zurück, den wir abschließend mit der im Module puppetlabs-stdlib enthaltenen Funktion parseyaml wieder in einen Hash der geforderten Form umwandeln.

class config(
  $options = {},
) {
  create_resources('config::setting',
    parseyaml(inline_template(
      '<%= @options.inject({}) {|h, (x,y)| h[x] = {"option" => x, "value" => y}; h}.to_yaml %>'))
  )
}
Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Paketstation

Mit FPM stellt Jordan Sissel, der führende Kopf hinter Logstash, ein Tool zur Verfügung mit dem man sehr einfach Pakete für die gängigsten Distributionen erstellen kann.

Für Effing Package Management (=FPM) muss Ruby und ein C-Compiler vorhanden sein:

# apt-get install ruby-dev gcc
# yum install ruby-devel gcc

Danach kann die eigentliche Installation des Ruby-Moduls erfolgen:

# gem install fpm

Als Beispiel für die Erstellung von Paketen mit FPM habe ich im Folgenden LConf Standalone Web gewählt, natürlich kann dies auch auf andere Softwareprodukte abgewandelt werden.

Zuerst muss bei LConf Web das Archiv mit dem Quellcode herunter geladen und entpackt werden, danach wechselt man wie gewohnt in das extrahierte Verzeichnis. Im Anschluss wird hier der configure-Befehl ausgeführt:

# ./configure --with-user=icinga --with-group=icinga

Bis zu diesem Zeitpunkt besteht noch kein Unterschied zum üblichen Installationsvorgang, erst bei make install unterscheidet sich der Aufruf. Statt die Dateien an den dafür vorgesehenen Stellen im Dateisystem abzulegen, leitet man sie mit dem DESTDIR-Parameter um:

# make install DESTDIR=/usr/local/src/lconf-web-1.4.0_installdir

Jetzt ist die Zeit von FPM gekommen, den hier kann nun das DESTDIR-Verzeichnis als Chroot verwendet und die darin enthaltene Verzeichnisstruktur “usr/local/lconf-web” beispielsweise in ein Debian-Paket integriert werden:

# fpm -s dir -t deb -n lconf-web -v 1.4.0 --deb-use-file-permissions \
-C /usr/local/src/lconf-web-1.4.0_installdir usr/local/lconf-web

Das dabei entstandene Paket lconf-web-1.4.0_amd64.deb kann nun natürlich mit dpkg auf einem Debian basierten System installiert werden.

Markus Waldmüller

Autor: Markus Waldmüller

Markus war bereits mehrere Jahre als Systemadministrator in Neumarkt i.d.OPf. und Regensburg tätig. Nach Technikerschule und Selbständigkeit ist er nun Anfang 2013 bei NETWAYS als Lead Senior Consultant gelandet. In seiner Freizeit ist der sportbegeisterte Neumarkter mit an Sicherheit grenzender Wahrscheinlichkeit auf dem Mountainbike oder am Baggersee zu finden.

Benutzung von Git zur Environment-Verwaltung bei Puppet mit r10k

Puppet_LogoNachdem ich nun schon jahrelang mit Git im Puppetumfeld kämpfe, gibt es nun mit dem Ruby-Skript r10k eine wunderbar nützliche Ergänzung. Mit r10k wird das Deployment von Branches als dynamische Environments zum Kinderspiel. Stellt sich natürlich zuerst die Frage, was versteht man unter dynamischen Environments?
Gemeint ist das Hinzufügen bzw. Löschen von Environments zur Laufzeit. Nur wozu das Ganze? Wenn ich z.B. an einem Puppetmodul arbeite will ich es nicht nur lokal mit puppet apply testen, sondern auch mit einem Puppetmaster. Dieses vorgehen wird spätestens zum Muss, wenn ich Exported Resources verwende möchte.
Genug der reinen Worten, ein skizziertes Beispiel wird es verdeutlichen. Wir starten mit der Konfiguration von Puppet für die Verwendung dynamischer Environments, hierzu passen wir die puppet.conf auf unserem Master an.

[main]
modulepath=/etc/puppet/environments/$environment/modules

71px-Git-logo.svgDas Verzeichnis /etc/puppet/environments muss vorhanden sein, die untergeordneten werden von r10k automatisch nach Bedarf angelegt. Nun benötigen wir ein GIT-Repository, aus dem r10k jeden Branch in ein Environment umwandelt. Das r10k-site.git genannte Repository erzeugen wir als Remote-Repository auf unserem Puppetmaster.

# git init --bare r10k-site.git

Bevor wir dieses mit Inhalt befüllen, ist r10k noch zu installieren

# gem install r10k

und zu konfigurieren. Letzteres ist in der Datei /etc/r10k.yaml vorzunehmen.

:cachedir: /var/cache/r10k
:sources:
  puppet:
    remote: "/var/repositories/r10k-site.git"
    basedir: /etc/puppet/environments

:purgedirs:
  - /etc/puppet/environments

Neben einem Cache-Verzeichnis, wird hier mit remote das Remote-Repository angegeben und das Verzeichnis, in das wir jeden Branch als Environment bauen lassen möchten. Mit purgedirs sorgen wir dafür, dass gelöschte Branche auch als Environment wieder entfernt werden.
Nun wählen wir uns einen Ort für ein git clone von r10k-site.git aus. In diesem legen wir dann die gewünschten Branches an, die dann zu den von uns gewünschten Environments zusammen gebaut werden. Jeder Branch muss hierbei eine Datei Puppetfile enthalten, in der wir, die in diesem Environment einzubauenden Module angeben.

forge "http://forge.puppetlabs.com"

mod "puppetlabs/stdlib", "3.2.2"

mod "tomcat",
  :git => "git@github.com:lbetz/puppet-tomcat.git",
  :ref => "0.3.2"

Ruft man nun das Ruby-Skript auf unserem Host auf dem r10k konfiguriert ist, z.B. mit

# r10k deploy environment -p

auf, werden alle Branches mit den in Puppetfile angegebenen Modulen als eigenes Environment nach basedir geschrieben. Die Option -p sorgt jeweils für erforderliche Updates der Module. Ein bestimmtes Environment kann mit

# r10k deploy environment <env_name> -p

erstellt werden. Packt man diese Aufrufe in einen Git-Hook, z.B. post-update, werden die Environments bei jedem Update des Repositories automatisch aktualisiert, angelegt oder gelöscht.

Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Puppenschnur im XML-Baum verheddert

Wer sich schon mal mit Puppet und XML-Konfigurationsdateien beschäftigt hat, weiß das dies ein schwer umzusetzendes Vorhaben in Puppet ist. Es gibt zwar eine Linse für Augeas, wer aber eine komplette XML-Datei mit Puppet verwalten möchte, wird damit schnell wahnsinnig.
Ich hatte vor einigen Wochen das Glück, mich bei einem Kunden, näher mit der Konfiguration vom Tomcat beschäftigen zu dürfen. Dabei stand ich vor dem Problem, z.B. folgende Struktur als server.xml mit Puppet erzeugen zu müssen.

<Server>
  <Service name=Catalina>
    <Engine name=Catalina>
      <Host name=...>
        ...
      </Host>
      <Host name=...>
        ...
      </Host>
    </Engine>
  </Service>
</Server>

Wobei in den Host-Sektionen natürlich auch nochmals Schachtelungen vorkommen. Mein erster Lösungsansatz war, mit XML Referenzen auf eigenständige Dateien zu verweisen, die damit eingebunden werden. Dies führt nun aber zu einem recht unübersichtlichen Datei-Tohuwabohu.
Die jetzige Lösung macht sich die Eigenschaften des Puppet concat-Moduls zu nutze. Die einzelnen Fragmente werden auf dem Puppet-Agent jeweils als separate Datei in ein temporäres Verzeichnis geschrieben, z.B. nach /var/lib/puppet/concat/_etc_tomcat_server.xml/fragments. Das Attribut order von concat::fragment dient hierbei als Prefix für den Dateiname des entsprechenden Fragments. Sind alle Fragmente geschrieben, liest ein Skript alle Dateien mittels find, sortiert lexikalisch nach Dateinamen und fügt die Fragmente abschließend mittels cat in die Zieldatei zusammen, hier /etc/tomcat/server.xml.
Schaut man sich dieses find näher an, stellt man fest, dass es nicht tiefenbeschränkt ist, d.h. es durchsucht auch weitere Unterverzeichnisse. Diese können vorab mit einer files-Resource angelegt werden und im order-Attribut mit angegeben werden.

concat { '/etc/tomcat/server.xml':
  mode => '0644',
} ->

file { '/var/lib/puppet/concat/_etc_tomcat_server.xml/50_host_A':
  ensure => directory,
}

concat::fragment { 'engine-header':
  target => '/etc/tomcat/server.xml',
  content => "\n",
  order => '40',
}

concat::fragment { 'engine-footer':
  target => '/etc/tomcat/server.xml',
  content => "\n",
  order => '60',
}

concat::fragment { 'host_A-header':
  target => '/etc/tomcat/server.xml',
  content => "\n",
  order => '50_host_A/00',
  require => File['/var/lib/puppet/concat/_etc_tomcat_server.xml/50_host_A'],
}

concat::fragment { 'host_A-footer':
  target => '/etc/tomcat/server.xml',
  content => "\n",
  order => '50_host_A/99',
  require => File['/var/lib/puppet/concat/_etc_tomcat_server.xml/50_host_A'],
}

Analog für weitere Hosts. Zusätzliche Fragmente für Host A lassen sich nun zwischen dem Header und Footer einordnen. Dies soll erstmal nur die Idee vergegenwärtigen. Ein komplexeres Anwendungsbeispiel findet sich unter https://github.com/lbetz/netways-tomcat.
Auch ist zu bedenken, das auf unterschiedlichen Hosts das temporärere concat-Verzeichnis nicht immer am selben Ort zu finden ist. Hier schaft ein selbstgeschriebener Fact Abhilfe.

# vardir.rb
Facter.add(:vardir) do
  setcode do
    Puppet[:vardir]
  end
end
Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.