Triple-D oder: Mit Docker ist alles testbar

Einführung in Test-driven Docker Development
Kommentare

Docker ist bei Entwicklern und Administratoren angekommen. Eine unfassbar schnelle Adaption von Docker ist im IT-Markt zu beobachten. Der Einsatz gelingt eigentlich überall, aber was ist eigentlich mit der Qualität der Produkte, die wir herstellen? Das Docker-Ökosystem ändert sich derart schnell, dass sich ferner die Frage nach der Zukunftsfähigkeit unserer Erzeugnisse stellt. Wie testen wir das Setup und das Zusammenspiel unserer Infrastruktur auf der nun alles so elegant abläuft? Fehler passieren, Sicherheitsmängel treten auf, und Änderungen sind heutzutage die Norm. Kann man ein Docker-Setup eigentlich gut testen oder sogar die Entwicklung der Infrastruktur im Test-First-Modus betreiben?

Die enorme Geschwindigkeit, mit der wir DevOps die Möglichkeiten der Docker-Technologie angenommen haben, ist beeindruckend und beängstigend zugleich. Leider gibt es bei so viel Licht auch immer etwas Schatten. Ein Dockerfile ist schnell geschrieben, und wir nutzen dabei viele Images und Artefakte von Fremden. Dies sollte jeden nachhaltig zum Nachdenken über die Bereitstellung unserer Anwendungen anregen. Wie verlässlich und vertraulich sind die Inhalte dieser aus dem Netz geladenen Container eigentlich? Nach kurzer Zeit beginnt man sich aus diversen Gründen seine eigene kleine Docker-Images-Plattform zu gestalten. Genau hier möchte der Artikel helfen, die Qualität dieser Docker-Entwicklung sicherzustellen. In einer zukunftsfähigen Infrastruktur sollte keine IT-Komponente laufen, die nicht änderbar, testbar und vermessbar ist.

Im Umfeld von Docker gibt es jede Menge Elemente, die einer Überprüfung standhalten sollten. Das Ergebnis der Bauanleitung des Dockerfiles soll überprüft werden können. Packages, Docker-Anweisungen und die Upstream-Base-Images ändern sich häufig (Berresch, Alexander; Jüntgen, Peter: „Image-Pflege“). Eventuell will man ganze Familien von Images erzeugen, um das eigene Projekt auf verschiedenen Betriebssystemen, Webservern, Web-Containern, Datenbanken oder Sprachversionen anzubieten. Damit definieren wir gleich die nächsten Testaufgaben: Die ablauffähigen und konfigurierbaren Docker-Container müssen überprüft werden. Docker-Container stapelt und verbindet man praktisch immer. Es entsteht natürlicherweise ein Verbund von Containern, der eine Anwendung oder gar ein ganzes System von verschiedenen Microservices bildet. Anschaulich wird das in den Konzepten von Kubernetes Pods, oder dem Werkzeug fig beschrieben. Aus dieser Erkenntnis ergibt sich direkt die nächste Aufgabe: Wie erfolgt eigentlich der Test einer ganzen Infrastruktur? Es ist die Bereitstellung von Maschinen, Netzwerken, Storage Volumes und Konfigurationen zu überprüfen. Die Aufgabe der Integrationstests von Anwendungen auf der Basis von Docker-Containern ist ein verwandtes Thema und wird im Artikel von Roland Huß beschrieben (Entwickler Magazin Spezial. Docker, Seite 50: Huß, Roland: „Ein mächtiges Werkzeug“). Diese Art von Testen ist eine meist unterschätzte Disziplin. An dieser Stelle hilft es, erst einmal durchzuatmen und nachzudenken. Will man diese Testaufgabe überhaupt annehmen? Wenn ja, mit welchen Methoden und Werkzeugen lässt sich die Aufgabe am besten meistern?

Gut, dass die IT schon vor dem Erscheinen von Docker für Probleme dieser Art Lösungen realisiert hat. Einer der umfangreichsten Ansätze, um „Infrastructure as Code“ zu testen, befindet sich im Projekt Test Kitchen. Es ist sehr nah mit dem Testen von Rezepten von Ruby und Chef verbunden, aber nicht mehr ausschließlich. Die Vielfalt und Möglichkeiten, die dort geboten werden, sind überzeugend. Leider sind der Lernaufwand und das Setup der Umgebung erheblich.

Im weiteren Verlauf wird nun ein Ansatz vorgestellt, der „einfacher“ und exklusiver auf die Testbarkeit von Docker-Artefakten und -Systemen eingeht.


Inside Docker: Wenn Sie mehr über Docker wissen möchten, empfehlen wir Ihnen das Entwickler Magazin Spezial Vol. 2: Docker zum leichten Einstieg in die Container-Virtualisierung.

docker-coverMit Docker feiern Linux-Container momentan ein eindrucksvolles Comeback. Während der Einsatz von virtuellen Maschinen viele Vor-, aber auch zahlreiche Nachteile mit sich bringt, ist Docker eine leichtgewichtige, containerbasierte Alternative, die die System-Level-Virtualisierung auf ein neues Level hebt. Dabei ergänzt Docker das Deployment von Betriebssystemen und Webanwendungen um die Lösungen, die man beim Original schmerzlich vermisst. In diesem Jahr hat Docker eine hohe Dynamik entwickelt und wird in allen aktuellen Linux-Distributionen wie Redhat, SUSE oder Ubuntu ausgeliefert. Firmen wie Spotify, Google, BBC, eBay und seit kurzem auch Zalando setzen Docker bereits produktiv ein. Das Entwickler Magazin Spezial „Docker“ informiert kompetent über diese revolutionäre Technologie, von der viele meinen, dass sie eine neue Ära in der IT einläuten wird. Wir erklären technische Hintergründe, demonstrieren Best Practices und zeigen, wie Docker effektiv eingesetzt werden kann. Das Sonderheft vereint umfangreiches Wissen über die wichtigsten Aspekte von Docker, spannende Ideen für eigene Docker-Projekte und wertvolle Impulse für ihre strategische Planung.

Test-First von Dockerfiles

Es ist immer gut, mit der Lösung von kleinen Aufgaben zu starten, um sich und dann mit den gewonnenen Erfahrungen den wirklich kniffligen Problemen zuzuwenden. Ein Dockerfile für die Bereitstellung eines Apaches ist schnell geschrieben, gebaut und bereitgestellt (Listing 1).

Listing 1: Apache httpd Dockerfile
FROM ubuntu:14.04
RUN apt-get -yqq update && apt-get install -yqq apache2
RUN mkdir -p /var/lock/apache2 /var/run/apache2 /var/log/apache2
EXPOSE 80
CMD ["/usr/sbin/apachectl", "-DFOREGROUND"]

Stopp! Ohne wirkliche Anforderung und Ziel ist dieses Image vielleicht für den späteren Einsatzzweck unbrauchbar. Typische Anforderungen an die Bereitstellung eines einfachen Webservers sind beispielsweise die folgenden:

  • Der Product Owner Franz möchte, dass die Bereitstellung der Anwendung änderbar, skalierbar, ressourcenschonend und kostengünstig erfolgt, damit weitere Projekte mit diesem Ansatz bald folgen.
  • Der Entwickler Henrich möchte seine Inhalte via HTTP bereitstellen und unter dem Port 8080 erreichbar wissen. Weiterhin möchte er mit derselben Version entwickeln, die auch in der Produktion genutzt wird. Die neue Infrastruktur sollte ermöglichen, weiterhin schnell Änderungen zu testen. Die Entwicklung basiert auf einem Apache Tomcat und wird in Java geschrieben.
  • Die Administratorin Wilma möchte die Logfiles aller Webserver zentral sammeln und auswerten können, damit sie Fehler erkennen und den Server optimieren kann.
  • Die Administratorin Wilma möchte die Version des Servers bestimmen und diese gegebenenfalls verändern können, ohne dass die bestehende Anwendung danach Probleme macht. Dies ist notwendig, damit Sicherheitsupdates schnell in die Produktion kommen, nachdem sie öffentlich geworden sind.

Hier stellt sich also die Frage: Wie beweist man nachhaltig, dass die Lösung den Anforderungen genügt? Vermutlich ist die Liste weder vollständig noch ausreichend. Schon länger nutzen wir für solche Aufgaben Tools, die versuchen, diese umgangssprachlichen Anforderungen direkt in Code zu beschreiben. Oftmals kommen dafür Ansätze des „Behavior-driven Development“ zum Einsatz. Für das Testen von Infrastrukturen ist das Ruby-Framework RSpec oder das darauf aufbauende Werkzeug Cucumber sehr populär. Weitere Implementierungen sind jBehave oder Spock.

Ein gutes Prinzip ist immer: klein starten. Deshalb formulieren wir den ersten Test, bevor die eigentliche Implementierung erfolgt. Eine der Anforderungen an das Image macht unsere bisherige Bereitstellung des Apaches unbrauchbar: Der Entwickler möchte seine Anwendung mit Java auf der Basis eines Apache Tomcats bereitstellen. Also formulieren wir nun, dass auf der Docker-Testinstallation zuerst eine eigene Tomcat-Installation verfügbar sein muss (Listing 2).

Bei dieser Testspezifikation kommt das Ruby-Docker-API zum Einsatz. Der Docker Daemon besitzt ein REST-API, für das neben dem Docker-CLI-Client eine Vielzahl von verschiedenen Sprach-Bindings existieren.

Listing 2: „tomcat_image_spec.rb“
require 'docker'
describe "apache tomcat8 image" do
    before(:all) {
        @image = Docker::Image.all().
        detect{|i| i.info['RepoTags'].
        detect{|r| r == "bee42/tomcat:8"}}
    }
    it "should be available" do
        expect(@image).to_not be_nil
    end
end

Damit diese Testspezifikation ausgeführt werden kann, muss eine Ruby- und Docker-Installation vorhanden sein. Für den Anfang reicht sicherlich boot2docker und eine lokale Ruby-Installation, aber schon bald wird man mehr wollen und sich seine eigene Docker-Welt schaffen.

Listing 3: Testinstallation und -ausführung
$ gem install rspec
$ gem install docker-api
$ mkdir spec
$ vi spec/tomcat_image_spec.rb
$ rspec spec/tomcat_docker_image_spec.rb
F

Failures:

  1) apache tomcat8 image should be available
     Failure/Error: expect(@image).to_not be_nil
       expected: not nil
            got: nil
     # ./spec/tomcat_image_spec.rb:9:in `block (2 levels) in '

Finished in 0.54932 seconds (files took 0.29816 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/tomcat_image_spec.rb:8 # apache tomcat8 image should be available

Der Test in Listing 3 schlägt natürlich fehl. Es ist keine Installation des Docker Images bee42/tomcat:8 vorhanden. Nun besteht unsere Aufgabe darin, mit einem entsprechenden Dockerfile die Installation zu prüfen (Listing 4). Das Ergebnis zeigt, dass die erste Anforderung nun erfüllt ist. Der Test ist grün. Dieses Ergebnis sollten wir nun als ersten Schritt in ein Source Repository, z. B. git, einchecken. Um den Ablauf wiederholbar und übertragbar zu machen, ist mehr erforderlich. Wir haben nicht sichergestellt, dass sich wirklich ein Tomcat in dem Image befindet, oder dass wir im Test das Image erzeugen, danach wieder löschen und so weiter. Der übliche testgetriebene Entwicklungsprozess, den wir seit Jahren erfolgreich in der Entwicklung betreiben, ist nun ohne Einschränkungen auf die Infrastrukturentwicklung übertragbar. Die Testausführungszeit von etwas mehr als 500 ms belegt, dass dies auch schnell mit Docker erfolgt. Natürlich brauchen komplexere Aufgaben mehr Zeit. Einen ähnlichen Ansatz mit virtuellen Maschinen zu implementieren ist möglich, stellt aber unsere Geduld unnötig lange auf die Probe. Ein verbessertes Dockerfile für ein Apache Tomcat Docker Image befindet sich in Listing 5. Eine produktive Version befindet sich im Projekt serverspecbox, und im Infrabricks-Blog finden Sie weitere Details.

Listing 4: Erfolgreich testen
$ export DOCKER_HOST=tcp://192.168.59.103:2375
$ mkdir -p docker/bee42/tomcat8
$ cd docker/bee42/tomcat8
$ echo "FROM ubuntu:14.04" > Dockerfile
$ docker build -t="bee42/tomcat:8" .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon 
Step 0 : FROM ubuntu:14.04
 ---> c4ff7513909d
Successfully built c4ff7513909d
$ cd ../../..
$ rspec spec/tomcat_images_spec.rb
.

Finished in 0.19616 seconds (files took 0.36187 seconds to load)
1 example, 0 failures

Das beschriebene TDD-Verfahren und die Umgebung des Projekts serverspecbox sind auf der Basis von Vagrant und Virtualbox realisiert (Abb. 1). Als Besonderheit befindet sich für die Lösung der Testaufgabe im Verzeichnis docker.d/ruby eine Docker-in-Docker-Entwicklungsumgebung, damit eine stärkere Trennung eines Entwicklungsprojekts auf einem Host gewährleistet ist. Mit dieser Form der Installation ist die gesamte Entwicklungsumgebung in einem Docker Image gespeichert (Abb. 2). Die Docker-in-Docker-Entwicklung garantiert Nachvollziehbarkeit. Sogar ein experimenteller Zwischenstand kann mit einem Kollegen geteilt werden, indem ein Snapshot mittels docker commit vom Dev-Container erzeugt wird und mit docker push in die eigene lokale Docker-Registry zum Test bereitgestellt wird. Es ist einfacher geworden, den Status der gesamten Entwicklung auf den Arbeitsplatz des Kollegen oder in die CI/CD-Pipeline zu übertragen. Mit diesem td3-Verfahren (sprich: Triple-D) können wir also nun jede weitere Anforderung an das Apache Tomcat Docker Image testgetrieben formulieren, implementieren, prüfen, verbessern und bereitstellen.

Abb. 1: td³ Specification Flow

Abb. 1: td³ Specification Flow

Abb. 2: Docker-in-Docker-Testumgebung

Abb. 2: Docker-in-Docker-Testumgebung

Listing 5: Einfacher Apache Tomcat 8 Dockerfile
FROM ubuntu:14.04
MAINTAINER Peter Rossbach <peter.rossbach@bee42.com>
RUN apt-get update && \
    apt-get install -yq openjdk-7-jre-headless
ENV TOMCAT_MAJOR_VERSION 8
ENV TOMCAT_MINOR_VERSION 8.0.14
ENV CATALINA_HOME /opt/tomcat
RUN apt-get install -yq wget
RUN wget -q https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \
    wget -qO- https://www.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \
    tar zxf apache-tomcat-*.tar.gz && \
    rm apache-tomcat-*.tar.gz && \
    mv apache-tomcat* ${CATALINA_HOME} && \
    rm -rf ${CATALINA_HOME}/webapps/* && \
    rm -rf ${CATALINA_HOME}/bin/*.bat

RUN adduser  --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --disabled-password --quiet tomcat && \
   echo 'tomcat:tomcat' | chpasswd
RUN chown -R tomcat:tomcat /opt/tomcat 
USER tomcat
WORKDIR /opt/tomcat
VOLUME /opt/tomcat/logs
VOLUME /opt/tomcat/webapps
EXPOSE 8080
EXPOSE 8009
CMD ["/opt/tomcat/bin/catalina.sh",  "run"]

Eine ganze Hostumgebung prüfen – geht das?

Die weitere Herausforderung liegt darin, die wirklichen Inhalte eines gestarteten Docker-Containers und seiner Hostumgebung zu überprüfen. Natürlich möchte man nicht nur das pure Image prüfen, sondern vor allem den laufenden Docker-Container. Das Testframework Serverspec unterstützt diese Anforderung vorbildlich. Erläuterungen und Beispiele zu der Verwendung von Serverspec sind ebenfalls auf dem Infrabricks-Blog zu finden. Serverspec unterstützt im aktuellen Release 2 nun RSpec 3 und ergänzt RSpec um Typen, Commands und Matcher, die eine einfache Abfrage von Infrastrukturtests erlauben. Damit die Shell-Kommandos auf verschiedenen Betriebssystemen und Zugangsmöglichkeiten ausgeführt werden können, existiert das Projekt Specinfra, das eigentliche Backend von Serverspec. Grob können die Resource Types in die Bereiche Netzwerk, Dateien, Betriebssystem, Anwendungskonfiguration und Prozesse unterteilt werden. Viele dieser Typen bringen dann spezielle Matcher mit, um die Formulierung von Tests lesbarer zu machen. Es ist damit einiges an Eleganz unter der Haube und zur Formulierung von Tests verfügbar (Abb. 3, Listing 6).

Abb. 3: Die Serverspec-Komponenten

Abb. 3: Die Serverspec-Komponenten

Mit dem Release 2.3 sieht die Prüfung der Existenz eines Docker Images dann sehr elegant aus (Listing 6). Es können alle Informationen des Befehls docker inspect überprüft werden.

Listing 6: Vereinfacher Docker Image-Test mit „serverspec ~> 2.3“
require 'serverspec'
set :backend, :exec
describe docker_image("bee42/tomcat:8") do
  it { should exist }
end
describe docker_image("bee42/tomcat:8") do
  its(['Config.Cmd']) { should include '/opt/tomcat/bin/catalina.sh' }
end

Auf die Konfiguration eines laufenden Docker-Containers kann ebenfalls einfach zugegriffen werden. Hier ist ein kleiner Trick aus dem Artikel „How to Use Docker on OS X: The Missing Guide“ von Chris Jones nützlich. Statt dem Standard „boot2docker-ISO“ wird ein ISO, das die VirtualBox-Tools enthält, genutzt. Auch boot2docker nutzt den Mechanismus eines Layered-File-Systems (LiveCD). Die Installation kann ausgetauscht werden, und zwar ohne dass die eigenen Daten verlorengehen. Mit den VirtualBox-Tools ist es dann möglich, das eigene /Users-Verzeichnis in die boot2docker VM zu mounten. Mit diesem Trick kann ein Verzeichnis des eigenen Macs in einem Container als Volume vermittelt werden (Listing 7). Mit dem Einsatz von boot2docker 1.3 ist diese Variante nun integriert.

Listing 7: Setup boot2docker und Starten des Tomcat-Docker-Containers
# stoppen aller Container nicht vergessen!
boot2docker down
curl http://static.dockerfiles.io/boot2docker-v1.2.0-virtualbox-guest-additions-v4.3.14.iso > ~/.boot2docker/boot2docker.iso
VBoxManage sharedfolder add boot2docker-vm -name home -hostpath /Users
boot2docker up
docker run -ti -d --name=tomcat8 -p 8180:8080 -p 8109:8009 \
-v 'pwd'/logs:/opt/tomcat/logs \
-v 'pwd'/webapps:/opt/tomcat/webapps \
bee42/tomcat:8

Der entsprechende Test beweist, dass dieses Developer-Setup tatsächlich gelingt. Diese Variante bedeutet eine gewisse Umgebungsabhängigkeit, aber genau deshalb lohnt es sich, die Testumgebung in einen eigenen Container zu verpacken. Wer möchte, kann dies mit Serverspec bzw. Ruby ausgleichen. Ein Blick in die Rubrik „Advanced Tips“ auf der Website von Serverspec lohnt sich. Der Einsatz von Properties ist einfach, und es ist ein guter Rat, die Specs nach Service-Roles zu organisieren.

Listing 8: Test des Setups
describe docker_container('tomcat8') do
  PWD = 'pwd'.chop
  it { should have_volume( '/opt/tomcat/logs', "#{PWD}/logs") }
  it { should have_volume( '/opt/tomcat/webapps', "#{PWD}/webapps") }
end

Aktuell wächst die Unterstützung für den Docker-Test-Support. Grundsätzlich sind die Möglichkeiten zum Testen in einem laufenden Container allerdings noch problematisch. Normalerweise läuft nur ein Prozess in einem Docker-Container. Wie sollen dann Tests im Container ausgeführt werden? Grundsätzlich bietet Docker 1.2 für die Ausführung von Tests im selben Namespace des laufenden Containers drei Möglichkeiten:. Die Testumgebung und Ausführung wird Bestandteil des Containers.

  1. Die Testumgebung und Ausführung wird Bestandteil des Containers.
  2. Die Tests werden via SSH-Shell ausgeführt, und der Container bietet diesen SSH-Service zusätzlich an.
  3. Die Tests werden via nsenter direkt im selben Namespace ausgeführt.

Die ersten beiden Lösungen sind sofort machbar, aber sie haben eine enorme Schwäche: Der Inhalt des Images wird gewaltig verändert (Abb. 4, links). Die direkte Einbindung des gesamten Testssetups ist sogar problematisch, da Compiler und Programmiersprachen zusätzlich eingebunden werden. Die zweite Lösung bedeutet, dass man einen Service-Wrapper für den Start aller Services braucht (Abb. 4, Mitte). Eigentlich sollte das lieber via systemd für alle Prozesse des Hosts geregelt werden. Die dritte Möglichkeit wäre ein guter Weg. Für die Tests werden nur Prozesse im selben Namespace des laufenden Containers gestartet (Abb. 4, rechts). Nichts Weiteres muss im Image bereitgestellt werden. Einige Serverspec-Tests benötigen kleine Helfertools, aber die könnten auch als weitere Test-Volumes hinzugefügt werden. Als Kern der Lösung dient der Befehl nsenter, der auf dem Host installiert sein muss. Für die Integration dieses zusätzlichen Shell Backends muss man serverspec, bzw. das Teilprojekt specinfra, erweitern. Andreas Schmidt hat dies für die Version 1.2.x erfolgreich probiert und im Infrabricks-Blog dokumentiert. Im Detail sind noch einige Verbesserung gewünscht. Die Übergabe der Container-ID ist beispielsweise schwierig. Im Docker-Release 1.3 ist eine direkte nsenter-Integration mit dem Befehl docker exec umgesetzt. Ein erstes Beispiel mit der nsenter-Integration befindet sich im Projekt serverspecbox auf dem Infrabricks-Blog (Listing 9). Sobald im Docker-Ruby-API die Funktionalität für docker exec bereitsteht, wird diese in das bestehende docker-serverspec Backend integriert.

Listing 9: „serverspec“ mit „nsenter“
require 'serverspec'
set :os, :arch => 'x86_64', :family => 'debian', :distro => 'debian', :release => 7
set :backend, :nsenter
set :docker_cid, "tomcat8"
set :nsenter_pid, nil

describe package('wget') do
  it { should be_installed }
end
Serverspec sh, SSH und nsenter im Docker-Container

Abb. 4: Serverspec sh, SSH und nsenter im Docker-Container

Fazit

Insgesamt sieht die Testbarkeit von Container-Images über Serverspec gut aus. Serverspec kann jede Menge nützliche Testhelfer anbieten, auch wenn hier jeder von uns bestimmt noch zur Verbesserung beitragen kann. Zumindest die statischen Aspekte von Docker lassen sich über den Ansatz im Dockerfile und mit dem Docker Backend von Serverspec gut testen. Wer Container als VM-Ersatz betreibt, hat in der Regel volle Testbarkeit über den SSH-Zugang. Sobald die Entwicklung um nsenter bzw. docker exec fortgeschritten ist, lässt sich die volle Testbarkeit sicherlich auch für den Microservice-Container-Ansatz erreichen. Um auf einer Maschine unterschiedliche Projekte und Versionen der Testinfrastruktur ablaufen zu lassen, empfiehlt sich eine Docker-in-Docker-Installation, damit verschiedene Versionen der Testumgebung sich nicht stören (Abb. 2).

Sicherlich bedarf es noch einiger Experimente, damit die Umgebung stabil ist. Die Organisation der Tests durch eine Rollenzuordnung zu jedem Service ist nützlich. Container treten meist in Gruppen auf, und somit braucht es eine Koordination des Testaufbaus und -abbaus. Ein Reporting und die Einbindung in eine CI, wie Jenkins oder Teamcity, ist einfach, muss aber realisiert werden. Diese Herausforderungen zu meistern, erfordert noch etwas Zeit und konkreten Einsatz. Im Infrabricks-Blog gibt es dazu sicherlich bald weitere Neuigkeiten zu lesen.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Aufmacherbild: Stack of Freight Containers at the Docks with Truck. 3d rendering via Shutterstock / Urheberrecht: 2M media

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -