Aus Linux-Magazin 10/2008

Konfigurations- und Change-Management mit Bcfg2

© pmphoto, fotolia .com

Wer Konfigurationen zentral verwalten will, muss die Einstellungen dennoch zu den Clients transportieren. Bcfg2 geht aber noch einen Schritt weiter und abstrahiert das Was vom Wie. Dabei mahnt die Software den Admin sogar selbstständig daran, was er noch nicht erledigt hat.

Da die Mathematik- und Informatikabteilung des Argonne National Laboratory [1] unzufrieden mit der manuellen Konfiguration ihrer vielen Systeme war, entwickelte sie als internes Forschungsprojekt Bcfg2 [2] und veröffentlichte es später unter der BSD-Lizenz. Die akademische Herkunft des Projekts ist erkennbar: Es nutzt moderne Techniken wie XML zur Beschreibung und RPC zur Kommunikation mit seinen Clients. Kenner erweitern es leicht, es erfordert aber eine gehörige Einarbeitungszeit, da es einen sehr weitsichtigen, aber auch abstrakten Weg wählt. Darin unterschiedet sich das Werkzeug von den eher pragmatisch orientierten Tools Cfengine oder Puppet (siehe Artikel in diesem Heft).

Bcfg2 unterstützt viele Plattformen, darunter Open Suse, Fedora, Gentoo und Debian sowie viele abgeleitete Derivate und Businessversionen. Zusätzlich kommt es auch mit FreeBSD, AIX, Solaris und Mac OS X klar, da die Entwickler zusätzlich zu den fertigen Paketen für Client (oft »bcfg2«) und Server (oft »bcfg2-server« genannt) auch distributionsunabhängige Encap-Packages anbieten [3]. Alternativ hilft ein Blick auf den Open Suse Build Service [4] weiter, der Pakete für einige weitere Plattformen anbietet.

Architektur

Die Verwaltung von Spezifikationen erledigt Bcfg2 mittels eines Servers, der mit einem relativ kleinen Gegenstück auf dem Client kommuniziert. Sowohl Server als auch Client spielt der Administrator initial mit dem Paketmanager seiner Wahl ein. Der Client besteht nur aus wenigen Zeilen Python-Code. Ihn trägt der Administrator entweder in die Crontab ein, damit sich der Client regelmäßig beim Server meldet und Aktualisierungen abholt. Oder er lässt Bcfg2 als Hintergrundprozess starten. In diesem Fall darf der Server den Agenten auch aktiv kontaktieren.

Der Admin konfiguriert alle Einstellungen seiner gemanagten Systeme ausschließlich am Bcfg2-Server. Er hält für jeden Client Beschreibungen vor, die er aus einer zentralen Spezifikation erzeugt. Dieser Prozess hat mehrere Abstraktionsschichten und erfordert für den Einsteiger eine gewisse Einarbeitung.

Objekthierarchie

So kennt Bcfg2 auf der obersten Ebene Profile, die Klassen gleichartiger Rechner beschreiben, etwa ein Desktopsystem oder einen Webserver. Jeder verwaltete Rechner hat genau ein Profil. Innerhalb dessen sind Gruppen definiert, die bestimmte logische Systembereiche umfassen, etwa Office-Software oder Netzeinstellungen. Sie erlauben es, die Konfigurationsspezifikation übersichtlich zu gestalten und Code-Duplikation zu vermeiden. Diese Gruppen lassen sich rekursiv schachteln. Jede Gruppe enthält ihrerseits eine beliebige Anzahl von Bundles. Ihr Geltungsbereich orientiert sich zumeist an einem Softwareprodukt, etwa Postfix, Open Office oder dem Nameswitch-Mechanismus. Die Profile, Gruppen und Bundes legt die Datei »metadata/groups.xml« fest (siehe Listing 1).

Listing 1: Definition von
Bundles und Subgruppen

01 <Groups>
02   <Group name='desktop' profile='true'>
03     <Bundle name='motd' />
04     <Bundle name='networking' />
05     <Group name='office-workstation' />
06     <Group name='debian-stable' />
07   </Group>
08 
09   <Group name='webserver' profile='true'>
10     <Group name='apache' />
11     <Bundle name='networking' />
12     <!-- ... --!>
13   </Group>
14 
15   <Group name='office-workstation'>
16     <Group name='gnome-desktop' />
17     <!-- ... --!>
18   </Group>
19 
20   <Group name='debian-stable'
21          toolset='debian' />
22 </Groups>

Pakete schnüren

Auch Bundles untergliedern noch weiter: Bcfg2 gruppiert alle zu einem Systemdienst gehörenden Konfigurationsdateien und Softwarepakete, jedes von ihnen in einer Datei im Verzeichnis »Bundler«. Die Datei »Bundler/motd.xml« definiert entsprechend das Bundle zur Konfiguration der Begrüßungsnachricht »/etc/motd«:

<Bundle name='motd' version='2.0'>
  <Package name='login' />
  <ConfigFile name='/etc/motd' />
</Bundle>

Einige gut kommentierte einführende Beispiele sind im Projekt-Wiki [6] zu finden. Die Bundles enthalten eine Anzahl von Konfigurationseinträgen, von Bcfg2 auch schlicht Entries genannt.

Das System kennt sechs verschiedene Typen von Entries (siehe Tabelle 1): »ConfigFile« verwaltet eine Datei mit Inhalten, »Directory«, »Permission« und »SymLink« erlauben es, genau die ihrem Namen entsprechenden Objekte zu verwalten oder zu beobachten. Mit etwas mehr künstlicher Intelligenz sind Entries der Typen »Package« und »Service« ausgestattet. Bcfg2 abstrahiert nämlich die jeweils distributionsspezifischen Methoden, wie es etwa ein Paket auf einem Client einspielen soll: Auf einem Debian-System ruft es »aptitude« auf, auf einem Open-Suse-System »rpm« oder »yum«. Das Gleiche gilt für Systemdienste, die zumeist in»/etc/init.d« verzeichnet sind.

Tabelle 1: Konfigurationstypen
eines Entry

Server installieren

Diese abstrakte Struktur, die den Aufbau des Clients beschreibt, verwaltet ausschließlich der Server. Der Admin richtet ihn auf der Kommandozeile mittels »bcfg2-admin init« ein. Nachdem er Fragen zum Pfad des Repository, zu einem SSL-Zertifikat für die sichere Kommunikation mit den Clients und zu einem Zugangspasswort beantwortet hat, legt das Bcfg2-Admin-Skript mehrere Dateien und Verzeichnisse an: Die Konfigurationsdatei »/etc/bcfg2.conf« füllt es mit sinnvollen Standardwerten.

Eventuell muss der Admin den Eintrag der Sektion »[components]« mit einem Hostnamen anpassen, den seine Clients per DNS auflösen können. Diesen Namen sollte er optimalerweise auch für den »Common Name« verwenden, wenn er das Zertifikat generiert. Die Datei legt »bcfg2-admin« eventuell mit Leserechten an, was der Administrator ändern sollte, da es auch das Zugangspasswort für die Clients enthält. Zusätzlich legt das Tool ein leeres Konfigurations-Repository unter »/var/lib/bcfg2« an. Der Aufruf von »/etc/init.d/bcfg2-server start« startet den Server.

Clients konfigurieren

Auf jedem Client installiert der Systemverwalter lediglich das Clientpaket »bcfg2« und hinterlegt die soeben erzeugte Konfigurationsdatei ebenfalls in »/etc/bcfg2.conf«. Dann nimmt er jeden Bcfg2-Client auf dem Server in die Datei »Metadata/clients.xml« auf (Listing 2). Alle Spezifikationen erfolgen im XML-Format. In dieser Datei weist der Admin den Hosts später Profile zu. Die Clients identifiziert Bcfg2 anhand ihrer DNS-Namen, daher ist ein laufendes DNS Pflicht. Das Beispiel weist den Hosts »alpha« und »beta.example.com« das Profil »dektop« und »gamma.example.com« entsprechend »webserver« zu. Bcfg2 ist nun bereit.

Listing 2: Anmelden von
Clients

01 <Clients>
02   <Client profile="desktop"
             name="alpha.example.com" pingable="N"/>
03   <Client profile="desktop"
             name="beta.example.com"  pingable="Y"/>
04   <Client profile="webserver"
             name="gamma.example.com" pingable="N"/>
05   <!-- .. --!>
06 </Clients>

Mehrstufiger Prozess

Ein Update-Prozess besteht aus mehreren Schritten (siehe Abbildung 1): Zunächst ruft der Client seine Konfigurationsbeschreibung in XML-Form vom Server ab, führt eine Inventur durch und vergleicht Soll- und Ist-Zustand. Nachdem er alle sich daraus ergebenen Änderungen durchgeführt hat, prüft er erneut die Unterschiede und teilt das Ergebnis dem Server mit, der daraus Reporte und Statistiken erzeugt. Um dies zu überprüfen, stößt der Systemverwalter zu Testzwecken ein Update an: Von einem Client aus startet er mit

bcfg2 -q -v -n

einen Aktualisierungprozess im No-Op-Modus (Option »-n«), der keine Änderungen durchführt. Die Option »-q« steht für Quick-Request und lässt einige Prüfungen aus. Durch »-v« erhält der Admin einige Zusatzinformationen. Das Ergebnis einer solchen Abfrage zeigt Listing 3.

Abbildung 1: Der Administrator erstellt in Bcfg2 eine Spezifikation der gewünschten Clientkonfigurationen. Bcfg2 versucht diese herbeizuführen und dokumentiert seinen Erfolg in Form einer ausführlichen Statistik.

Abbildung 1: Der Administrator erstellt in Bcfg2 eine Spezifikation der gewünschten Clientkonfigurationen. Bcfg2 versucht diese herbeizuführen und dokumentiert seinen Erfolg in Form einer ausführlichen Statistik.

Automatisches Erkennen

Da die Spezifikation noch leer ist, zeigt Bcfg2 weder korrekte (Zeile 5) noch inkorrekte Entries (Zeile 6) an, weil der Server ja noch keinen einzigen Konfigurationseintrag zentral verwaltet (Zeile 7). Spannend ist die Angabe der »unmanaged entries« in Zeile 8: Der Bcfg2-Client hat ohne explizites Zutun des Administrators 242 Konfigurationsobjekte gefunden, die der Server zukünftig optimalerweise registrieren und verwalten sollte.

Dies sind hauptsächlich Entries der Typen »Service« und »Package«, die die verwendete Linux-Distribution auf dem Client eingespielt hat. Die Zeile 2 gibt darüber näher Auskunft: Bcfg2 hat automatisch erkannt, dass es sich um eine Distribution handelt, die den »chkconfig« zum Konfigurieren von Init-Diensten nutzt und die RPM als Paketmanager verwendet. Bei einem Debian-System stünde ein Verweis auf »update-rc.d« und »DEB« als Paketformat.

Listing 3: Unkonfigurierter
Server

01 Loaded tool drivers:
02  Chkconfig    POSIX        PostInstall  RPM
03 
04 Phase: initial
05 Correct entries:        0
06 Incorrect entries:      0
07 Total managed entries:  0
08 Unmanaged entries:      242
09 
10 Phase: final
11 Correct entries:        0
12 Incorrect entries:      0
13 Total managed entries:  0
14 Unmanaged entries:      242

Admin als Lückenfüller

Da der »bcfg2«-Aufruf eben ein Update ohne Änderungen angefragt hat, stimmen die Angaben in den Zeilen 10 bis 14 mit den darüberliegenden Werten überein. Die Aufgabe des Systemverwalters ist es nun, sukzessive die unverwalteten Enträge zu sichten und gegebenenfalls zu spezifizieren. Fügt er dem verwendeten Kommando noch die Option »-e« hinzu, zeigt das System die Einträge detailliert an. Ein Stärke von Bcfg2 ist, eine anfangs lückenhaft erfasste Konfiguration nach und nach zu konkretisieren, bis das Tool letztlich die gesamte Softwarekonfiguration abdeckt.

Der Update-Prozess (Abbildung 2) ist das Herzstück jeder Konfigurationsverwaltung, daher bedarf er einer detaillierten Betrachtung. Er besteht aus drei Phasen: In der »initial«-Phase verbindet sich der Client mit dem Server und führt eine Reihe von Tests durch, die so genannten Probes. Diese kurzen Shellskripte führt der Client aus. Abhängig vom Ergebnis fügt der Server der Clientspezifikation zusätzliche Gruppen oder Bundles hinzu. Ein Test gibt dazu lediglich als Ergebnis »group:Gruppenname« aus.

Abbildung 2: Die mehrstufige Kommunikation mittels XML-RPC löst der Bcfg2-Client aus, der Tests und Konfigurationen anfordert und einen Report liefert.

Abbildung 2: Die mehrstufige Kommunikation mittels XML-RPC löst der Bcfg2-Client aus, der Tests und Konfigurationen anfordert und einen Report liefert.

Der Server weist dem Client automatisch alle zusätzlichen Gruppen zu. So lässt sich etwa automatisch ein passendes Bundle für ein Modem einbinden, wenn »lspci« es auf dem Client entdeckt hat. Beispiele und eine ausführliche Anleitung finden sich unter [5].

Konfigurationsprozess

In der zweiten Phase erhält der Client die beschriebene konkrete Konfigurationsbeschreibung vom Server, in der viele Konfigurationseinträge der sechs Basistypen stehen. Durch die lokale Inventur baut er eine vergleichbare Struktur auf, die er anschließend vergleicht. Bei Abweichungen führt der Client Änderungen durch, die vom Typ des zugehörigen Konfiguationselements abhängen: Unterscheiden sich zwei Konfigurationsdateien, ersetzt der Bcfg2-Client sie durch die Version des Servers.

Ist ein Dienst nicht gestartet, holt dies der Client nach. Abhängigkeiten zwischen Konfigurationselementen löst er automatisch auf. Diesen Prozess wiederholt der Client so lange, bis er keinen weiteren Fortschritt mehr macht. Ein weiterer Aufruf des Update-Prozesses würde keine weiteren Änderungen nach sich ziehen.

Meldung machen

In der abschließenden dritten Phase generiert der Client eine Nachricht, die neben dem Systemstatus weitere Details enthält, darunter die Anzahl der korrekten sowie der nicht konformen Konfigurationseinträge und der Anzahl noch nicht vom Bcfg2-Server erfasster Objekte auf dem System. Diese Nachricht schickt er zum Server, der sie in Form von Webseiten, RSS-Feeds und E-Mails aufbereitet.

Der Kern des Bcfg2-Systems ist die Konfigurationsspezifikation. Administratoren beschreiben damit die gewünschten Zielkonfigurationen ihrer verwalteten Systeme. Dies geschieht in zwei Stufen: Die bereits beschriebene Struktur aus Profilen, Gruppen und Bundles nennt Bcfg2 Metadaten. Sie legen nur fest, welche Elemente Bcfg2 für einen Client konfgurieren soll. Wie diese Konfiguration pro Client aussieht, definiert die so genannte konkrete Konfiguration.

Fragt ein Client beim Server an, konstruiert dieser aus den passenden Metadaten die abstrakte Konfiguration (siehe Abbildung 3). Sie ist eine Vorlage, die alle benötigten Konfigurationseinträge des Zielsystems ohne inhaltliche Informationen enthält. Beispielsweise steht in einem »ConfigFile«-Element ein Dateiname, jedoch noch nicht ihr Inhalt.

Abbildung 3: Als ersten Schritt stellt der Bcfg2-Server aus den Metadaten, Gruppen und Bundles die abstrakte Konfiguration eines Clients zusammen.

Abbildung 3: Als ersten Schritt stellt der Bcfg2-Server aus den Metadaten, Gruppen und Bundles die abstrakte Konfiguration eines Clients zusammen.

Generatoren

Nachdem der Server die Struktur einer Konfiguration zusammengestellt hat, bindet er mittels so genannter Generatoren an jeden Eintrag konkrete Informationen. Bcfg2 bringt einige in Python geschriebenen Generatoren mit, die der Admin in der Datei »/etc/bcfg2.conf« mit

generators = Cfg, Pkgmgr, Rules, TCheetah

einbindet. Jeder registrierte Generator ist in der Lage, eine gewisse Menge an Konfigurationselementen zu instanzieren. In einfachen Fällen liefert der »Cfg«-Generator eine statische Datei, bei etwas komplexeren Einstellungen erzeugt ein »TCheetah«-Generator mittels einer Template- und Skriptsprache den Datei-Inhalt aus einem Datenbankeintrag. Andere Generatoren kümmern sich um Konfigurationselemente wie Services und Packages. Auf diese Weise lassen sich praktisch beliebige Datenquellen nutzen, um die konkrete Konfiguration zusammenzustellen (siehe Abbildung 4).

Abbildung 4: Bcfg2 kennt mehrere Generatoren, die abstrakte Einträge der Spezifikation mit Inhalten füllen. So entstehen aus den Bundles mit Metadaten vollständige, konkrete Konfigurationen.

Abbildung 4: Bcfg2 kennt mehrere Generatoren, die abstrakte Einträge der Spezifikation mit Inhalten füllen. So entstehen aus den Bundles mit Metadaten vollständige, konkrete Konfigurationen.

Cfg

Das Cfg-Plugin produziert Inhalte vornehmlich für Entries des Typs »ConfigFile«. Um ein solches Element mittels Cfg zu konfigurieren, legt der Systemverwalter ein Subdirectory unterhalb von »Cfg« im Repository an, das den gleichen Namen wie das zugehörige Bundle trägt. In dieses Verzeichnis legt er schlicht eine statische Datei, die Bcfg2 in der Folge an alle Clients verteilt.

Hängt er an den Dateinamen ein Suffix an, steuert er, welche Clients sie erhalten sollen. Die Endung »H_Hostname« verteilt die Datei nur an den angegebenen Rechner, »GPrio_Gruppe« schickt sie an alle Systeme, deren Profil die angegebene Gruppe enthält. Hat ein Host mehrere passende Gruppen, verwendet Bcfg2 die Datei mit der höchsten Priorität.

Zugriffsberechtigungen definiert der Admin mittels einer Datei »:info« im selben Verzeichnis, die hier die Einträge

owner: root
group: admin
perms: 0644

enthält. Darüber hinaus darf er noch einige Werte, beispielsweise das Encoding oder das Verhalten von Bcfg2 bei lokalen Änderungen, bestimmen. Mit der Funktionalität des Cfg-Generators lassen sich bereits große Teile eines Systems verwalten. Dennoch bietet er nicht in allen Fällen die notwendige Flexibilität, um große Systeme zu warten.

TCheetah

Daher haben die Bcfg2-Entwickler den TCheetah-Generator – basierend auf der gleichnamigen Template-Sprache Cheetah – entwickelt [7]. Der hohen Flexibilität steht der Umfang einer weiteren Sprache gegenüber, die der Admin lernen muss. Cheetah ermöglicht es, Anweisungen von einfachen String-Operationen über Flusskontrolle bis hin zu vollständigen Python-Anweisungen direkt in die Konfigurationsdateien einzubetten.

TCheetah lehnt sich an die Verzeichnisstruktur des Cfg-Generators an. Entsprechend repräsentieren Verzeichnisse unterhalb von »/var/lib/bcfg2/TCheetah« die Konfigurationselemente. Sie enthalten eine »info«-Datei mit den gleichen Inhalten wie beim Cfg-Generator und die Konfiguration mit dem Namen »template«. In der Datei trägt der Administrator Cheetah-Code ein und darf auch einige Bcfg2-spezifische Erweiterungen verwenden. In »/var/lib/bcfg2/TCheetah/etc/motd/template« aufgenommen entsteht aus Listing 4 eine dynamische Begrüßungsnachricht. Der TCheetah-Generator ersetzt dabei »$self.metadata.hostname« mit dem konkreten Wert und führt die durch »#for« eingeleitete Schleife aus.

Listing 4: Skript für eine
automatisierte »motd«

01 Willkommen auf $self.metadata.hostname!
02 
03 Bcfg2 konfiguriert dieses System. Es
04 befindet sich in folgenden Gruppen:
05 
06 #for $group in $self.metadata.groups:
07  * $group
08 #end for

Anschluss gesucht

Wer eine Datenbank anbindet, erschließt weitere Datenquellen zur Konfiguration. So lassen sich automatisch die DHCP-, DNS- und NIS-Konfigurationen aus den gleichen Ursprungsdaten generieren, etwa einem Verzeichnisdienst. Das Beispiel in Listing 5 zeigt, wie TCheetah unter Debian aus den Daten einer PostgreSQL-Datenbank die Netzwerkinterfaces in »/etc/network/interfaces« konfiguriert.

Listing 5: TCheetah
konfiguriert»network/interfaces«

01 #from Bcfg2.Server.dbconnection import DBPgConnection
02 #silent result = DBPgConnection().execute(
03    "SELECT ip, netmask, broadcast, gateway 
04     FROM hosts 
05     WHERE hostname = '%s'" % $self.metadata.hostname)
06
07 auto eth0
08 iface eth0 inet static
09     address   $result[0]
10     netmask   $result[1]
11     broadcast $result[2]
11     gateway   $result[3]

Services

Konfigurationen bestehen nicht nur aus Datei-Einträgen. Systemdienste etwa wollen je nach Runlevel ebenfalls konfiguriert sein. Der Servicegenerator liest die Angaben des Admin aus »Svcmgr/services.xml«, um zu erfahren, wie etwa der NTP-Dienst zu konfigurieren ist. Dazu referenziert der Server den Dienst, der im zugehörigen Bundle durch »<service name=\’ntpd\’ />«. verzeichnet ist. In »services.xml« legt der Admin fest, ob der Client den Dienst starten soll oder nicht:

<Services priority='0'>
   <Service name='ntpd' status='on' />
</Services>

Die Angaben formt der Generator in eine konkrete Konfiguration um und sendet sie an den Client, der dann die distributionsspezifischen Methoden anwendet, um den Dienst zu aktivieren oder zu deaktivieren.

Packages

Bcfg2 ersetzt nicht den Paketmanager, ist aber in der Lage, die typischen Paketmanager der von ihm verwalteten Client-Distributionen zu instrumentieren. Damit ist das System unabhängig von der Plattform. Allerdings überträgt es mehr Kontrolle vom jeweiligen Paketmanager auf den Systemverwalter, da der bei Bcfg2 pro Paket festlegen darf, welche Version das Werkzeug installiert. In XML-Dateien verwaltet er die Informationen über alle verfügbaren Pakete und kann sie mit der Paketauswahl eines Installationsservers synchronisieren oder manuell pflegen. Mehrere Installationsserver ermöglichen es, unterschiedliche Prioritäten festzulegen, etwa für Sicherheits-Updates.

Der Bcfg2-Client vergleicht die global spezifizierten Paketversionen mit den lokal vorhandenen und führt davon abhängig Up- oder Downgrades durch. Der Admin friert eine spezielle Version ein, indem er ihr die höchste Priorität zuweist.

Außer den vorgestellten Generatoren konfigurieren weitere auch die anderen Elementtypen wie Verzeichnisse, Symlinks und vieles mehr. Ambitionierte Anwender binden zusätzlich zu den von Bcfg2 mitgelieferten Generatoren eigene Python-Funktionen mittels Plugins an Konfigurationselemente.

Dokumentation in Sicht

Die vitale Entwickler-Community von Bcfg2 integriert fast wöchentlich neue Konzepte in das System. Vor Kurzem hat sich auf der Mailingliste [8] ein neues Team formiert, das sich um die Verbesserung der Dokumentation bemühen will. Die ist auch notwendig, da das Werkzeug eine Menge Einarbeitung in Details fordert. Die belohnt es durch maximale Flexibilität, Anpassbarkeit an lokale Bedürfnisse und vor allen durch Plattformunabhängigkeit.

Admins schätzen den sanften Migrationspfad des Werkzeugs, der die Einführung erleichtert. Sie vervollständigen allmählich die Konfigurationsspezifikation. Wer mit dem hohen Abstraktionsniveau zurechtkommt, findet mit Bcfg2 ein mächtiges Werkzeug.

Infos

[1] Argonne National Library, Mathematics and Computer Science Division:[http://www-new.mcs.anl.gov/new/]

[2] Bcfg2: [http://www.bcfg2.org]

[3] Bcfg2 Encap Packages: [http://trac.mcs.anl.gov/projects/bcfg2/wiki/EncapPackages]

[4] Bcfg2-Pakete im Open Suse Build Service:[http://download.opensuse.org/repositories/home:/markojung:/bcfg2/]

[5] Bcfg2 Probes: [http://trac.mcs.anl.gov/projects/bcfg2/wiki/Probes]

[6] Bcfg2 Annotated Examples:[http://www.bcfg2.org/wiki/AnnotatedExamples]

[7] Cheetah Template Engine:[http://www.cheetahtemplate.org]

[8] Bcfg2-Mailingliste:[mailto:bcfg-dev@mcs.anl.gov]

Der Autor

Marko Jung unterstützt als IT-Berater kleine und mittelständische Unternehmen bei Migrationen und dem Einsatz von freier Software.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben