Aus Linux-Magazin 10/2019

Wie die Cloud Design-Entscheidungen bestimmt

© tribalium123, 123RF

Die Cloud ändert Design-Prinzipien von Applikationen und Plattformen grundlegend. Worauf müssen Entwickler achten? Und wie passen Admins ihre Plattformen den neuen Gegebenheiten an?

Führt man sich die Bedeutung von Innovationen vor Augen, erstaunt es doch, dass bis heute viele Entwickler und Admins an Design-Prinzipien festhalten, die ihren 20. Geburtstag lange hinter sich haben. Dabei hat kaum ein anderes Konzept die IT so radikal verändert wie die Idee des Cloud Computing.

Was mancher Admin anfangs nur sehr widerwillig zur Kenntnis nahm, hat sich vom Trend längst zu einem allgegenwärtigen Betriebskonzept gemausert. Trotzdem rücken viele Admins, wenn sie Setups in Clouds und Clouds selber planen, immer noch mit den Ansätzen aus vorrevolutionärer Zeit an.

Das passiert zweifellos aus einer Vielzahl von Gründen. Zunächst entwickelt, wer sich viel im Cloudumfeld bewegt, eine Sonderform der Betriebsblindheit. Er meint, dass die Vorteile der Cloud ja auf der Hand liegen und dass jeder entsprechend froh sein muss, sie ausnutzen zu dürfen. Die Realität ist anders: Längst nicht jeder Admin beschäftigt sich regelmäßig mit Cloudsetups. Viele Admins, die sich heute darum kümmern, tun das zum ersten Mal – und müssen dabei ebenso radikal umdenken wie die Early Adopter vor beinahe einer Dekade.

Mangelnde Adaption kostet

Hinzu kommt, dass die Cloud mit einer Vielzahl althergebrachter Standards aufräumt, mit denen mancher Admin sich nur allzu gern eingerichtet hat. Persistente Daten, Hochverfügbarkeit per Clustermanager, wenige monolithische Komponenten, die es einfach machen, den Überblick zu behalten – mit all diesen Dingen ist in der Cloud Schluss, zumindest theoretisch. Denn viele Admins wenden bekannte Prinzipien gnadenlos auf virtualisierte Setups und Cloudumgebungen an.

Zu ihrem eigenen Nachteil, bringen sie sich damit doch um einen großen Teil der Vorteile, die eine Cloud grundsätzlich bieten kann. Im schlimmsten Falle haben sie am Ende mehr Arbeit als vorher. Schon meint man vor dem inneren Ohr das Meckern zu hören, wie unnütz das Konzept der Cloud sei und wie unsicher und instabil die Cloud.

Neue Spielregeln

Aber damit nicht genug: Während viele Admins noch damit beschäftigt sind, sich mit Clouds anzufreunden, rollt bereits die nächste Welle durch die IT. Docker hat Container (wieder) salonfähig gemacht. Applikationsentwickler ersparen sich damit den Overhead, den vollvirtualisierte Server mit sich bringen. Werkzeuge wie Kubernetes helfen ihnen dabei, Container effizient zu verwalten. Links wie rechts ist es also möglich und nötig, Applikationen anders zu bauen als jene in der Vergangenheit.

Fakt ist: Die Cloud genauso wie Container stellen an ihren Workload ganz andere Anforderungen, als ein konventionelles Setup es tut. Es ist aus Sicht der App-Entwickler deshalb sinnvoll, Rücksicht auf die lokalen Gegebenheiten einer Cloud zu nehmen und die jeweilige Applikation an die Zielumgebung anzupassen.

Dasselbe gilt umgekehrt allerdings ebenso: Wer weiß, dass er nur einen bestimmten Workload in seiner Cloud wird laufen haben – und dabei ist es ganz egal, ob der auf Open Stack, AWS oder Kubernetes läuft –, kann seine physische Umgebung an diese Anforderungen anpassen. Im Idealfall spart das eine Menge Geld, weil Infrastruktur gar nicht erst ausgerollt wird, die ohnehin unnütz wäre.

Dieser Artikel beleuchtet beide Perspektiven. Er geht einerseits auf die Frage ein, inwiefern Clouds und Container die Applikationen beeinflussen, die auf ihnen laufen. Andererseits zeigt er die Abzweigungen auf, die Nutzer wie Admins im Falle von Private Clouds nehmen können, um sich Arbeit zu sparen.

Thema Mobilität

Eine der zentralen Fragen, die Admins wie Nutzer sich im Kontext einer Cloud stellen müssen, ist die, ob Hochverfügbarkeit auf der Ebene einer Applikation implementiert sein soll oder auf Ebene der Plattform. Und das ist für das Design von beiden Komponenten von größter Bedeutung (Abbildung 1).

Abbildung 1: Pacemaker war früher ein Tool, um Apps hochverfügbar zu machen..

Abbildung 1: Pacemaker war früher ein Tool, um Apps hochverfügbar zu machen..

Gegeben sei etwa eine klassische, monolithische Applikation der älteren Bauart. Wer eine solche Applikation in die Cloud verschiebt, nutzt meist nur die IaaS-Funktion der Cloud, und das üblicherweise mit voller Virtualisierung. Es kommt also KVM zum Einsatz, das einen kompletten virtuellen Rechner simuliert und darin die jeweilige Anwendung betreibt. Hochverfügbarkeit lässt sich auf der VM-Ebene über Clustermanager implementieren, was allerdings eine äußerst unelegante Lösung ist.

Die Alternative besteht darin, sich auf Ebene der Plattform um die Verfügbarkeit der VM zu kümmern. Dann spricht man von einer Applikation, die mobil sein muss – ganz konkret ist es notwendig, sie im laufenden Betrieb per Live-Migration von einem Host auf einen anderen verschieben zu können. Das ist vor allem aus Sicht des Anbieters bedeutsam, denn der muss in der Lage sein, seine einzelnen Server zu warten, ohne dass es großes Geschrei gibt, falls ein Server mal für ein Kernelupdate rebootet.

Diese Notwendigkeit geht mit ganz klaren Erwartungen an die Umgebung einher. Soll eine Cloud Workloads bedienen, die diese Art der Mobilität bieten, dann bedeutet das zunächst: Lokaler Speicher in den Computeknoten ist tabu. Sehr zum Leidwesen der Nutzer, denn hat eine virtuelle Maschine in Open Stack ihr Root-Dateisystem etwa auf Ceph liegen, ist das signifikant viel langsamer als es wäre, wenn es auf lokalem NVME-Speicher des Hostsystems läge.

Ohne geteilten Netzwerkspeicher würde die Live-Migration aber nicht funktionieren, die nötig ist, um den Reboot einzelner Server zu ermöglichen. Hier spielt sich das Dilemma ab, vor dem Admins seit Jahren stehen, wenn sie Clouds bauen: Wollen sie schnellen lokalen Speicher, der nicht mobil ist – oder langsamen Speicher im Netz, der Stunts wie Live-Migration erlaubt?

Massive Auswirkungen

Eben diese Frage hat massive Auswirkungen auf die Art und Weise, wie der Admin seine Plattform plant und implementiert. Denn für diese Form der Mobilität zahlt er erhebliche Gebühren in Form technischer Nachteile, etwa weil zwangsläufig ein verteilter Netzwerkspeicher zum Einsatz kommen muss (Abbildung 2).

Abbildung 2: Ob persistenter Speicher nötig ist oder nicht, entscheidet zu einem großen Teil darüber, wie komplex das physische Setup wird.

Abbildung 2: Ob persistenter Speicher nötig ist oder nicht, entscheidet zu einem großen Teil darüber, wie komplex das physische Setup wird.

Wer eine Public Cloud betreibt, kann sich kaum aussuchen, ob er diese Form der Mobilität anbieten will oder nicht. Denn hier ist der Dienstleister gezwungen, seinen Kunden ein möglichst großes Spektrum zu offerieren. In Private Clouds ist das möglicherweise aber schon wieder anders. Denn hier kann der Admin vielleicht Einfluss darauf nehmen, welcher Workload später in der Cloud laufen soll und welcher nicht.

Legt man etwa als Mantra fest, dass auf die zu bauende Cloudplattform nur Apps kommen, die dem Cloud-Native-Ansatz folgen, bringt das eine erhebliche Erleichterung: Ein fixer Bestandteil dieses Ansatzes ist es, dass Clouds sich um ihre Hochverfügbarkeit selbst kümmern. Dazu laufen in der Regel mehrere Instanzen derselben Applikation gleichzeitig, die im Falle eines Falles für den Ausfall einer ihrer Instanzen gewappnet sind. Das heißt: Im Underlay der Plattform kann dann ein Computeknoten ausfallen, etwa wegen eines Reboots nach dem Einspielen von Patches. Die Applikation im Overlay der Cloud wird sich davon nicht beeindrucken lassen.

Wenig Wahl bei Containern

Die bisher notierten Umstände beziehen sich übrigens fast ausschließlich auf klassische IaaS-Dienstleistung per Vollvirtualisierung. Bei Containern ist die Lage ab Werk wieder etwas anders. Das gilt zumindest, wenn man seine Container per Flottenmanagement betreut, wie es in Kubernetes der Fall ist.

Kubernetes geht wie selbstverständlich davon aus, dass die in ihm gehosteten Apps den Cloud-Native-Grundsätzen folgen. Nicht zuletzt bietet Kubernetes ja selbst diverse Werkzeuge und Hilfsmechanismen, um Hochverfügbarkeit für solche Apps auf der API-Ebene auch tatsächlich abzubilden – der Artikel geht darauf später ein.

Es ist in Kubernetes deshalb schwieriger, an einen Container persistenten Speicher anzuschließen, als es in Open Stack der Fall wäre. Klar: Am Ende braucht auch jede Cloud-Native-Applikation in einer spezifischen Art persistenten Speicher. Aber selbst hier geht ein Container in Kubernetes anders an die Sache heran als klassische VMs. MySQL ist ein gutes Beispiel: In Container-Szenarien läuft das meist unter der Ägide des Clustermanagers Galera. Und der kommt problemlos mit einem Szenario klar, in dem eine der Galera-Instanzen ausfällt.

Übrigens: Wer in Containern persistenten, hochverfügbaren Speicher braucht, sollte sich Rook gut anschauen. Rook bringt den Objektspeicher Ceph in Form von Containern in Kubernetes ein – im Hintergrund greift es dabei auf persistente Volumes in Docker zu, die auf den Servern in der Regel nur lokal vorhanden sind. Aber weil Ceph selbst inhärent hochverfügbar ist, merken Anwender davon nichts. Stürzt ein Knoten eines Rook-Clusters ab, laufen die Applikationen, die darauf zugreifen, wie gewohnt weiter (Abbildung 3).

Abbildung 3: Wer Ceph per Rook in Kubernetes betreibt, bekommt direkt in der Umgebung persistenten Speicher, der nicht vom Underlay abhängig ist.

Abbildung 3: Wer Ceph per Rook in Kubernetes betreibt, bekommt direkt in der Umgebung persistenten Speicher, der nicht vom Underlay abhängig ist.

Damit ist klar: Wenn sich alle Beteiligten – Admins wie Nutzer – schon vor dem Deployment einer Cloud darauf einigen können, dass klassische HA im Sinne von VM- oder Container-Mobilität nicht nötig ist, ist das hilfreich. So erspart sich der Admin nämlich den Betrieb von Infrastruktur, die eigentlich gar nicht erforderlich ist, während Applikationsentwickler gezwungen sind, sich gleich an die ehernen Grundsätze der Cloud-Native-Architektur zu halten.

Wie umgehen mit großen Kubernetes-Umgebungen?

Eine Frage, vor die Admins sich regelmäßig gestellt sehen, ist die nach dem effektiven Betrieb vieler Kubernetes-Instanzen. Innerhalb der IT-Community herrscht schon seit einiger Zeit ein Streit darüber, ob denn Kubernetes eine gute Alternative zu Open Stack sei oder nicht. Kubernetes kommt mit wesentlich niedrigeren Einstiegshürden daher und zwingt den Admin dazu, sich mit wesentlich weniger neuen Inhalten zu beschäftigen, als die große Private-Cloud-Umgebung Open Stack es tut.

Im Gegenzug fehlt bei Kubernetes aber auch einige Flexibilität, die bei Open Stack zum Lieferumfang gehört. Das beste Beispiel dafür ist die Fähigkeit, mehrere Mandanten innerhalb derselben Kubernetes-Plattform unterzubringen. Ja, das geht mittlerweile irgendwie. Aber im Gegensatz zu Open Stack war es bei Kubernetes nie vorgesehen, mehrere Mandanten innerhalb derselben Umgebung zu beheimaten. Aufsätze, die das dennoch möglich machen, brechen also fast immer mit Kubernetes-Konventionen und sind ein Kompromiss.

Wer Kubernetes und Open Stack direkt vergleicht, wird beiden Lösungen nicht gerecht. Open Stack baut eine mandantenfähige Cloud für den Einsatz als Public oder Private Cloud, die verschiedene Dienstleistungen wie IaaS erbringt. In aller Regel tut sie das auf Basis von KVM oder eines anderen Vollvirtualisierers. Kubernetes hingegen orchestriert Container und übernimmt dabei den größten Teil des Managements der Applikation – wenn diese entsprechend programmiert ist. Deshalb sind Open Stack und Kubernetes eigentlich auch keine Konkurrenten. Mit einer Kombination aus den beiden Produkten löst der Admin aber ein Problem, dessen Lösung andernfalls sehr komplizierter wäre.

Kubernetes auf Open Stack

Denn im Alltag der Kubernetes-Nutzung ergibt sich aus der fehlenden Mandantenfähigkeit die Frage: Wie betreibt der Admin viele Kubernetes-Instanzen ohne echtes Blech? Da kommt Open Stack wieder ins Spiel: Es bietet durch seine Mandantenfähigkeit eben diese Option und wird zum idealen Steigbügelhalter für Kubernetes.

Grundsätzlich funktioniert das so: Zunächst baut der Admin auf Basis von Open Stack eine Cloud nach gängigen Standards. Das ist heute nicht mehr so kompliziert wie noch vor einigen Jahren, denn auch bei Open Stack hat sich einiges getan. Auf Basis von Centos und der passenden Pakete des RDO-Projekts etwa lässt sich in relativ kurzer Zeit eine Cloud auf Basis von Open Stack aus dem Boden stampfen, entsprechende Anleitungen finden sich im Netz [1].

Wenn persistenter Speicher nötig ist, etwa weil sich künftige Nutzer und Admins nicht auf den Verzicht auf Mobilität in Sachen VMs einigen konnten, bietet der Objektspeicher Ceph dafür eine gute Lösung: Ceph ist freie Software und integriert sich hervorragend in Centos und Open Stack – die Open-Stack-Entwickler haben darauf stets größten Wert gelegt.

Variable Flexibilität

Wie komplex das Open-Stack-Setup werden muss, hängt übrigens auch wieder von den lokalen Bedingungen ab. Ein echtes Open Stack hat sowohl Software Defined Networking als auch Software Defined Storage im Schlepptau. Beide Komponenten machen das Setup deutlich komplexer. Auch hier gilt deshalb die Maxime, zunächst die eigenen Anforderungen zu klären.

Im Kontext von Software Defined Networking ist der gesamte Open-V-Switch-Zinnober etwa dann nicht notwendig, wenn die virtuellen Instanzen stattdessen ein bestehendes Netz als Providernetzwerk verwenden. Alle gestarteten virtuellen Server haben dann IP-Adressen in diesem Netz, und die verhassten Networking Nodes fallen weg. Dafür kann sich aber auch nicht mehr jeder Klient die eigene Netzwerktopologie konstruieren. Wie so oft tauscht der Admin also Funktionalität gegen verringerte Flexibilität ein.

Kubernetes betreiben

Der Rest ist dann quasi Schema F. Erhält ein Kunde der Private Cloud von deren Admins Login-Daten für einen Klienten in Open Stack, lassen sich darin auf Basis der definierten Quotas virtuelle Maschinen – meist solche mit KVM – starten. Und in eben diesen virtuellen Maschinen lässt sich danach Kubernetes betreiben. Die Entwickler der Apps haben mehrere Optionen, um ihr Kubernetes an den Start zu bringen. Was immer geht, ist die Installation zu Fuß, die allerdings nicht sonderlich effizient ist.

Besser geeignet für die Skalierung ist da der Orchestrierungsansatz: Mittels Open Stack Heat rollen Anwender etwa problemlos ganze virtuelle Setups aus, die benötigten VMs für die Kubernetes-Controller und die Kubelet-Instanzen lassen sich da prima im Rahmen des “Infrastructure as Code”-Ansatzes einbauen.

Wer Kubernetes nicht auf eigene Faust ausrollt, sondern sich dabei die Hilfe eines Werkzeugs wie Rancher ([2], Abbildung 4) oder Open Shift ([3], Abbildung 5) zunutze macht, ist ohnehin fein raus: Praktisch alle Kubernetes-Distributionen kommen mit schmückendem Beiwerk, um das automatische Ausrollen der Container-Orchestrierung in Open Stack ab Werk zu unterstützen. Der Admin trägt in dem jeweiligen Programm dann bloß noch die API-Endpunkte sowie seine Login-Daten ein – fertig ist der Lack, und dem Containervergnügen steht nichts mehr im Weg.

Abbildung 4: Rancher rollt eine gebaute Containerumgebung bei Bedarf auch direkt in Open Stack aus.

Abbildung 4: Rancher rollt eine gebaute Containerumgebung bei Bedarf auch direkt in Open Stack aus.


Abbildung 5: Überall gilt: Kubernetes auf Clouds ist sinnvoll.

Abbildung 5: Überall gilt: Kubernetes auf Clouds ist sinnvoll.

Besser geht es nicht

Wer als Admin also in die Verlegenheit kommt, viele Cluster auf Kubernetes-Basis zu betreiben, kann das kaum effizienter als mittels einer privaten Cloud auf Open-Stack-Basis tun. Der Overhead, der durch KVM entsteht, ist nicht groß genug, um stattdessen die Hampelei mit echtem Blech und dessen diversen Animositäten im Kontext der Automatisierung zu rechtfertigen.

Ferner erlaubt es eine Open-Stack-Cloud als Basis für Kubernetes dem Admin, jederzeit Ressourcen vorzuhalten, falls mal unerwartet ein paar Kubernetes-Instanzen hinzukommen. Wer stattdessen auf echtes Blech setzt, hat mit deutlich mehr Hürden beim Skalieren zu tun.

Admins sollten deshalb keine Angst vor Open Stack haben – auch nicht, weil dessen Komplexität im ersten Augenblick übermächtig wirkt. In den letzten Jahren haben alle großen Hersteller funktionale Open-Stack-Distributionen auf den Markt gebracht, die auch im Kontext kleinerer Setups Sinn ergeben. Und wer auf Basis von Ubuntu oder Centos weiterhin die Bastellösung bevorzugt und weiß, was er tut, behält freilich auch diese Option uneingeschränkt.

AaS-Dienste nutzen

Ein anderer Faktor, der die Verwendung von VMs und Containern in Clouds auf vielfältige Art und Weise beeinflusst, sind die As-a-Service-Funktionen, die in vielen Public und Private Clouds bereitstehen. Wer es gewohnt ist, konventionelle Setups zu planen, denkt dabei meist in vollständigen Umgebungen. Ist etwa eine Datenbank nötig, kauft man dafür einen eigenen Server oder im besten Falle gleich einen Datenbank-Cluster.

Das läuft dem Cloudansatz aber in vielerlei Hinsicht zuwider, denn hier übernehmen die Anbieter vielerorts zunehmend die Aufgabe, solche Infrastrukturdienste als As-a-Service-Komponente anzubieten. Wer bei AWS etwa eine MySQL-Datenbank braucht, klickt sich eine entsprechende DBaaS-Instanz.

Das verlockende Moment für den Admin ist, dass er sich um Betrieb und Pflege der Datenbank nicht mehr kümmern muss. Auch Backups legt die Datenbank oder eher der von AWS um sie herumgestrickte Rahmen automatisch an. Und die Funktion steht längst nicht mehr nur in AWS und Azure zur Verfügung – wer auf Open Stack setzt, bekommt in Form von Trove ebenfalls eine DBaaS-Komponente mit einigen Funktionen.

Noch zu häufig Handarbeit

Die gelebte Praxis zelebriert stattdessen echte Handarbeit. Selbst wenn es um Container geht, pfriemeln nicht wenige Admins in diese noch immer ihre Datenbanken und halsen sich damit deren Wartung auf. Der nächste Tipp in diesem Artikel ist deshalb: Wer in der Umgebung, in der er unterwegs ist, die Möglichkeit hat, auf Aas-Dienste zu setzen, der möge von dieser Möglichkeit umfassend Gebrauch machen.

Wer auf Tools wie Rancher & Co. für seine Kubernetes-Deployments setzt, kann diese in der Regel per Template so erweitern, dass sie nicht nur ein fertiges Kubernetes in einer Open-Stack-Umgebung ausrollen, sondern auch gleich passende Datenbanken dazu. Das spart Arbeit und verlagert einen Teil der Aufgabe dorthin, wo er viel besser abzuarbeiten ist.

Die Kommunikation der Dienste

In Containerumgebungen wie Open Stack ist es nicht trivial, die einzelnen Teile einer Applikation sinnvoll miteinander kommunizieren zu lassen. Früher galt die Faustregel: ein Dienst, ein Port – fertig. Eine Cloud-Native-App hingegen basiert auf einer Mikroarchitektur aus etlichen Diensten, die über definierte Schnittstellen miteinander kommunizieren müssen. Kubernetes nimmt dem Admin einen Teil der Arbeit ab, indem es sich im Hintergrund um Details wie Proxys und dergleichen kümmert.

Ideal ist dieses Szenario aber nicht. Es sei an dieser Stelle daher auf Istio und den entsprechenden Artikel in diesem Heft verwiesen: Istio baut ein Mesh-Netz im Inneren von Mikro-Apps auf, das festgelegte, generische Schnittstellen bietet und dem Entwickler so auch die Möglichkeit einräumt, den Überblick zu behalten.

Ohne CI/CD geht es nicht

Nicht zuletzt sei darauf hingewiesen, dass CI/CD-Systeme unbedingt ein fester Bestandteil jedes Container-App-Konzepts sein müssen. Das liegt schon an der Art und Weise, wie Kubernetes mit solchen Applikationen umgeht: Sie liegen in Containern, die je nach Bedarf einfach ausgetauscht werden. Um aus einem Ordner auf der Festplatte jedoch einen funktionalen Container zu machen, sind einige Arbeitsschritte nötig. Würde man sie händisch ausführen, wäre das einerseits höchst ineffizient und andererseits würde es nicht skalieren.

Sinnvoll ist stattdessen ein Ansatz etwa auf Basis von Jenkins, das ein Git-Verzeichnis überwacht und bei Commits automatisch betroffene Abbilder neu baut. Das entbindet den Admin einerseits von der Notwendigkeit, irgendwelche Abbilder aus dem Netz zu verwenden, die zu einer Blackbox würden. Und es produziert andererseits absolut reproduzierbare Resultate mit Audit Trail und der Möglichkeit, exakt festzustellen, wann eine eventuelle Fehlfunktion zum ersten Mal aufgetreten ist.

Übrigens: Es empfiehlt sich aus Sicht des Entwicklers, unbedingt auf die Container-Basis-Abbilder zu setzen, die die jeweiligen Distributoren anbieten. Denn bei denen lässt sich der Herstellungsprozess klar dokumentieren. Und sie sind in aller Regel deutlich effizienter als jene Container, die der Admin sich auf Basis eines leeren Ordners mit Werkzeugen wie »debootstrap« im Falle von Debian selbst basteln könnte. Ferner stellt sich die Vertrauensfrage hier eh nicht: Wer die Distribution eines Herstellers installieren würde, hat – rein logisch betrachtet – keinen Grund dazu, exakt demselben Hersteller zu misstrauen, wenn es um Containerabbilder geht.

Fazit

Wer Applikationen für den Betrieb in Clouds und Containern vorbereitet, sieht sich mit diversen Anforderungen an das Design der Applikation konfrontiert. Die meisten davon sind aus typischen Setups der Vergangenheit unbekannt. Wichtig ist die frühe Festlegung, ob es egal sein darf, falls eine Komponente einer Microservice-Applikation ausfällt. Von dieser Frage hängt die Komplexität des physischen Setups auf der einen Seite und des App-Designs andererseits ab.

Wer viele Kubernetes-Instanzen betreiben möchte, tut das idealerweise auf Basis einer Cloudplattform. Ob das AWS oder Azure als Public Clouds oder Open Stack als Private Cloud sein dürfen, hängt viel mehr von den jeweiligen Umständen vor Ort ab als von konkreten technischen Vorgaben. Ganz gleich, wo man seine Container-Workloads betreibt: CI/CD-Systeme zum automatischen Ausrollen der Container sind nötig.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 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
Nach oben