Virtualisierter Server - Puppet strukturieren mit Profilen, Environments, r10k und git
In diesem Beitrag lege ich dar, wie ich mein Puppet-Setup strukturiere. Ich orientiere mich dabei am Pattern der Profiles und Roles, habe es aber auf meine Bedürfnisse hin angepasst. Zum Einsatz kommt natürlich Git und es werden mehrere Repos gepflegt, die alle mit Hilfe von r10k dynamisch auf den Puppet Server gebracht werden, unter Beachtung von Environments etc.
Bisherige Beiträge in der Reihe:
- apt-Pakete zentral bereitstellen mit apt-cacher-ng
- Mail-Relay für die VMs mit Postfix und Sendgrid
- SSL überall mit Let’s Encrypt, verteilt durch Puppet
- Puppet Server aufsetzen
- pfsense-Firewall zur Einteilung des Netzwerks mit ipv4 und ipv6
- IPv6-Vorüberlegungen
- Hardware-Setup und Proxmox
Struktur
Wir fangen in der Praxis an und kommen dann zur Theorie anhand der Beispiele:
Ich habe ein Git-Repo puppet
, in dem liegen, neben
einem deploy-Script und einem Gemfile
zwei
weitere Git-Repositories als Submodule:
puppet
├── bootstrap
│ ├── bootstrap.pp
│ ├── common.sh
│ ├── deploykey
│ ├── deploykey.pub
│ ├── deploy_puppet.sh
│ └── puppetmaster.sh
├── environments
│ ├── data
│ ├── hiera.yaml
│ ├── manifests
│ └── Puppetfile
├── Gemfile
├── Gemfile.lock
├── pdeploy
└── profiles
├── data
├── files
├── hiera.yaml
├── manifests
└── templates
Bootstrap
Hier pflege ich einige kleine Scripte und ein Puppet-Manifest, mit deren Hilfe ich eine neue VM oder einen neuen Container für die Verwendung von Puppet vorbereite.
Der Inhalt ist nicht weiter interessant, es wird im Grunde nur das zum
jeweiligen Debian-Release
(grep CODENAME /etc/lsb-release|cut -d'=' -f2
)
passende Paket von apt.puppetlabs.com
heruntergeladen, installiert und nach einem generellen Systemupdate der
Puppet Agent installiert.
Die Installation des Puppet Servers ist schon interessanter: zuerst
passiert das gleiche wie auf einem Client, es wird aber auch
r10k
installiert, ein SSH-Schlüssel
deploykey
(mit lesendem Zugriff auf die Git-Repos)
übertragen und mit r10k
gleich das erste Mal das
Puppet-Setup auf den Puppet Server heruntergeladen. Wir sehen
später, wie das geht.
Profiles
In diesem Verzeichnis liegen Profile. Im Grunde handelt es sich dabei
um ein Modul, in dem mehrere Manifeste liegen, eins pro Profil.
Standardeinstellungen dafür werden in data/
mit
Hiera hinterlegt, was natürlich mit der
hiera.yaml
konfiguriert werden kann.
Ein Profil beinhaltet, wie jedes gute Manifest, eine class. Diese
wird als Wrapper für eine bestimmte Server-Funktion betrachtet. Im
Beitrag zum Einrichten eines Mail-Relay für die
VMs
etwa kann man das Profil mailrelay
betrachten: Die
für die Funktion des Mail-Relay notwendigen Konfigurationsdaten werden
ermittelt, die notwendigen Klassen aus anderen Modulen werden
instanziert und mit den ermittelten Einstellungen konfiguriert. Dabei
ist zu beachten, dass hier nicht etwa Postfix installiert oder
seine Konfigurationsdateien in Templates zerlegt wurden, die dann
befüllt werden! Das macht alles das Postfix-Modul, das ist eine andere
Ebene der Implementierung. Ein Profil kümmert sich nicht darum, wie
etwas installiert wird - man legt nur fest, was installiert wird und
was daran konfiguriert wird, etwa anhand von Parametern und Defined
Types. Das
fügt der Sache eine Abstraktionsebene hinzu: Einer VM weist man also
nicht zu, dass dort Postfix mit Konfigurationdatei
/etc/postfix/main.cf
mit Inhalt
xzy
installiert werden soll, sondern dass sie das
Profil eines mailrelay
erfüllen soll. Um die
Umsetzung kümmert sich dann das Profil. Das Profil wiederum installiert
auch nicht direkt das Paket postfix
und schreibt
in seine Konfigurationsdateien; vielmehr wird dort eine Klasse postfix
instanziert, die das alles macht und dieser Klasse wird nur mitgeteilt,
welche Konfigurationseinstellungen man wünscht. Die Klasse postfix aus
dem gleichnamigen Modul setzt dann schlussendlich genau das um.
Wäre für den Betrieb des mailrelay
etwa noch ein
MySQL-Server notwendig, könnte man im Profil
mailrelay
auch dafür eine Klasse aus einem Modul
instanzieren und zum Beispiel Zugangsdaten dafür über eine gemeinsam
verwendete Variable an die Module postfix und mysql übergeben, die
sich dann um die jeweils für sie relevanten Details der Implementierung
kümmern.
Unverkennbarer Vorteil dieser Systematik ist natürlich die
Wiederverwendbarkeit der verschiedenen Schichten. Das Modul
letsencrypt
(aus dem Beitrag zur Einrichtung von
SSL
etwa kann sowohl im Profil sslmaster
als auch im
Profil ssl
(für Clients) verwendet werden, mit
unterschiedlichen Setups. Genauso können Profile unterschiedlich
konfiguriert und auf verschiedenen Hosts daher mit verschiedenen
Einstellungen genutzt werden.
Environments
Die Struktur dieses Repositories ist etwas komplexer. Jedes in meinem
Puppet-Setup vorhandene
Environment
wird dort in einem eigenen Branch gepflegt. Nachdem es bei Puppet
normalerweise kein Environment master
gibt sondern
production
, habe ich das entsprechend auch im
Repository so abgebildet.
Es gibt eine Puppetfile
, in die ich alle
verwendeten Module eintrage:
mod 'profiles',
:git => '[email protected]:puppet/profiles.git',
:branch => 'master'
mod 'puppetlabs-firewall', '1.9.0'
mod 'puppetlabs-postgresql', '5.1.0'
# [...]
Man sieht hier zwei Dinge: Ich lasse mir das
profiles
-Repository von meinem Git-Server holen.
Dazu muss auf dem Puppet Server ein SSH-Key liegen, der zumindest
lesenden Zugriff auf das Repo hat. Deswegen vorhin der
deploykey
. Zweitens rufe ich verschiedene Module
anhand ihres Namens und (optional) eine Version aus der Puppet Forge
ab. r10k
kann damit umgehen und holt mir den Code
automatisch von dort. Ich muss also nirgends in meinem Setup Check-Outs
der verwendeten 3rd-Party-Module pflegen. Das ist ein Vorteil; zu meinem
Nachteil kann ich so aber keine lokalen Forks pflegen, bei denen etwa
kleine Änderungen am Code gemacht wurden.
Weiterhin gibt es hier meine zentrale hiera.yaml
,
welche die Konfiguration der Nodes und Dienste bereitstellt. Wie man so
eine Datei aufbauen kann, sieht man beispielhaft im Beitrag zu
verschlüsselten Einträgen in Hiera mit
eyaml.
Dort wird auch die ganze Konfiguration der Profile vorgenommen.
Beispielsweise wird profiles::mailrelay::relayhost
auf die Adresse meines SMTP-Relayhosts im Internet gesetzt. Erst jetzt!
Von hier geht diese Konfiguration an das Profil
mailrelay
, das wiederum schickt es weiter an die
Klasse postfix
und die trägt es schlussendlich an
die richtigen Stellen ein. Wie gesagt: Das steigert die
Wiederverwenbarkeit des Codes.
Die Zuweisung von Profilen an VMs oder Container findet hier auch statt, nicht nur die Fein-Konfiguration der Profile. Ein Server kann dabei mehrere Profile zugewiesen bekommen:
# Datei environments/data/nodes/puppet.domain.tld
classes:
- puppetdb
- puppetdb::master::config
- profiles::sslmaster
- profiles::mail
Am Beispiel meines Puppet Servers sieht man, dass man Profile einfach
durch neue Elemente im Array classes
zuweisen
kann. Ich habe außerdem noch ein Profil baseline
,
in dem ich allgemeine Dinge wie Generierung von
locales
, Installation von
vim
, etc machen lasse, die auf allen Systemen
angewandt wird. Diese Klasse weise ich dann einfach in der
common.yaml
zu. Hier greifen alle normalen
Mechaniken von Hiera. Damit das dann klappt, braucht man noch ein
kurzes Manifest:
# Datei environments/manifests/site.pp
lookup('classes', Array[String], 'unique').include
Hier werden alle Arrays mit Namen classes
zusammengetragen und mit include
geladen. Nachdem
das Manifest auf jedem Zielhost einzeln realisiert wird, werden
natürlich nur diejenigen Klassen geladen, die auch auf den Host gehören.
r10k
Damit r10k
funktioniert, muss es initial
aufgesetzt werden. Das ist bei mir Teil des
deploy_puppet.sh
-Scripts und des dazugehörigen
Puppet-Manifests und erstreckt sich im Grunde auf die drei Ressourcen
im Manifest:
sshkey { 'git.domain.tld':
ensure => present,
type => 'ssh-ed25519',
key => 'AB...XYZ',
}
file { 'deploykey':
ensure => present,
path => '/root/.ssh/id_rsa',
source => '/tmp/deploykey',
owner => 'root',
group => 'root',
mode => '0600',
}
class { 'r10k':
remote => '[email protected]:puppet/environments.git',
provider => 'puppet_gem',
}
und vier Zeilen im Script:
scp deploykey puppet.domain.tld:/tmp
scp bootstrap.pp puppet.domain.tld:/tmp
ssh puppet.domain.tld 'sudo /opt/puppetlabs/bin/puppet module install puppet/r10k'
ssh puppet.domain.tld 'sudo /opt/puppetlabs/bin/puppet apply /tmp/bootstrap.pp'
Workflow
Hat man dieses Setup erfolgreich aufgesetzt, kann man Änderungne an Profilen oder an der Konfiguration in den dazugehörigen Repositories auf der Workstation vornehmen und bei Bedarf an den Git-Server schicken. Ist der Push angekommen, muss noch ein Pull auf dem Puppet Server stattfinden, damit der Code auch dort ankommt. Dazu genügt:
sudo r10k deploy environment -pv
Damit wird das vorgegebene Repository environments
ausgecheckt und die darin liegende Puppetfile
gelesen. Die dort definierten Referenzen auf externe Repos (darunter
profiles
!) werden auch umgesetzt und der Code wird
in das Verzeichnis
/etc/puppetlabs/code/environments/<namedesenvironments>
ausgecheckt. Für das Environment production
ist
also alles schon mal im Gange. Wie machen wir jetzt andere Environments?
Git branches als environments
Um ein neues Environment anzulegen, schafft man einfach einen neuen
Branch des Repositories environments
:
git branch sslsetup
git checkout sslsetup
# jetzt Änderungen vornehmen, dann:
git commit
git push origin sslsetup
Wenn der Branch auch remote auf dem Git-Server besteht, wird
r10k
ihn ebenfalls auschecken, nämlich nach
/etc/puppetlabs/code/environments/sslsetup
- ebenfalls mit den Inhalten seiner eigenen
Puppetfile
und was dazugehört. So können also auch unterschiedliche Versionsnummern von Modulen je nach Branch verwendet werden.
Wo sind jetzt hier die Rollen aus dem Pattern?
Um ehrlich zu sein habe ich in meinem Setup bisher noch keine Rollen verwendet. Das liegt zum einen daran, dass es derzeit noch organisch wächst, nachdem mein virtualisiertes Netzwerk zuallererst eine Lern- und Testlabor ist und zum anderen bisher einfach noch keine Installationen gemacht wurden, bei denen mehr als ein Server von einem bestimmten Typ (Rolle) gebraucht wird. Ich habe also etwa noch nicht zwölf gleiche Hosts, die alle die gleiche Kombination aus Profilen tragen. Die Zuordnung Nodes zu Rolle wäre also 1:1, da kann ich sie mir auch sparen. Bei späterem Bedarf wird hier dann eben refaktoriert.