{"id":6852,"date":"2020-07-12T10:39:45","date_gmt":"2020-07-12T08:39:45","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=6852"},"modified":"2020-07-12T10:39:46","modified_gmt":"2020-07-12T08:39:46","slug":"typescript-in-einem-docker-container-auf-dem-raspberry-pi-ausfuhren-einsteiger-tutorial","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/typescript-in-einem-docker-container-auf-dem-raspberry-pi-ausfuhren-einsteiger-tutorial\/","title":{"rendered":"TypeScript in einem Docker-Container auf dem Raspberry Pi ausf\u00fchren (Einsteiger-Tutorial)"},"content":{"rendered":"<p>Docker sowie Docker-Compose sind auch f\u00fcr die ARM-Architektur und damit den Raspberry Pi verf\u00fcgbar. Meist werden sie zwar auf x86-Hardware eingesetzt. Doch auch auf dem Raspberry hat man zunehmend mehrere Anwendungen im Einsatz: Beispielsweise einen Anwendungs- oder API-Server, der in vielen F\u00e4llen nicht ohne Datenbank auskommt. Nicht selten m\u00f6chte man diese Daten auch noch grafisch darstellen, sodass eine Benutzerschnittstelle dazu kommt. Schnell haben wir mehrere Container, die bisher direkt auf dem Pi installiert wurden &#8211; wie auf einem klassischen Linux-Server.<\/p>\n<p>Durch die Verwendung von Containern k\u00f6nnen wir diese Anwendungen besser voneinander isolieren. Die Vorteile sind identisch wie auch auf klassischen x86 Systemen. <\/p>\n<h2 class=\"wp-block-heading\">Voraussetzungen<\/h2>\n<ul class=\"wp-block-list\">\n<li>Raspberry Pi 3 oder neuer (Je neuer das Modell, um so besser die Performance)<\/li>\n<li>SSH-Zugang eingerichtet<\/li>\n<li>Docker und Docker-Compose installiert<\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">Anlegen der Ordnerstruktur<\/h2>\n<p>Zun\u00e4chst legen wir einen Basisordner f\u00fcr das Projekt an und navigieren mit cd dort hin<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">mkdir my-project\ncd my-project<\/code><\/pre>\n<p>Die Trennung von Quellcode und weiteren Dateien wie Konfigurationsdateien hat sich bew\u00e4hrt. Daher ist zu empfehlen, einen weiteren Unterordner f\u00fcr den Source Code anzulegen. Oft wird er <strong>src<\/strong> genannt. Ich empfehle diese Konvention zu \u00fcbernehmen. Grunds\u00e4tzlich kann er aber frei benannt werden.<\/p>\n<p>In diesem Szenario handelt es sich um eine einzelne Anwendung, die wir selbst entwickeln. Dazu kommen Drittanbieter-Abh\u00e4ngigkeiten wie z.B. eine Datenbank. An dieser Stelle sollte man sich \u00fcberlegen, welche Komponenten die eigene Anwendung ben\u00f6tigt. M\u00f6chte man beispielsweise Backend und Frontend trennen (Eigenentwicklung der grafischen Oberfl\u00e4che) oder ben\u00f6tigt zus\u00e4tzliche Dienste wie einen Cron, macht es Sinn, diese direkt in Unterordnern aufzuteilen. Im folgenden Beispiel halten wir es einfach und legen nur einen src-Ordner an, der mit <strong>index.ts<\/strong> den Einstiegspunkt der Anwendung enth\u00e4lt. <\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">mkdir src\necho &quot;console.log(&#039;Hello World&#039;)&quot; &gt; src\/index.ts<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Docker-Compose Datei erstellen<\/h2>\n<p>In der ersten Ordnerebene (hier <strong>my-project<\/strong>) legen wir zun\u00e4chst eine <strong>docker-compose.yml<\/strong> Datei mit einem frei w\u00e4hlbaren Texteditor (z.B. vim) an. Sie definiert alle Dienste, die zu unserer Anwendung geh\u00f6ren und in getrennten Docker-Containern gestartet werden.<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">version: &#039;2&#039;\nservices:\n  app:\n    build:\n      context: src\n    mem_limit: 256MB\n    ports:\n        - 80:80\n        - 443:443\n<\/code><\/pre>\n<p>Hier definieren wir zun\u00e4chst nur einen Dienst f\u00fcr die Anwendung selbst. Diese k\u00f6nnte z.B. eine API bereitstellen. Weitere Container wie beispielsweise Datenbanken k\u00f6nnen nat\u00fcrlich hinzugef\u00fcgt werden, je nachdem was man ben\u00f6tigt.<\/p>\n<h2 class=\"wp-block-heading\">TypeScript mittels package.json installieren<\/h2>\n<p>NodeJS unterst\u00fctzt TypeScript nicht automatisch. Zun\u00e4chst muss der TypeScript-Kompiler installiert werden. Er erzeugt JavaScript, das NodeJS ausf\u00fchren kann. Der Kompiler ist im NPM-Paket <strong>typescript<\/strong> zu finden. Man kann ihn entweder mit<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">npm i -g typescript<\/code><\/pre>\n<p>global innerhalb des Containers installieren. Oder \u00fcber die lokale <strong>package.json<\/strong>. Ich empfehle letzeres, da man die package.json oft ohnehin f\u00fcr weitere Bibliotheken ben\u00f6tigt. Dadurch befinden sich alle Abh\u00e4ngigkeiten zentral an einer Stelle, statt in package.json und Dockerfile.<\/p>\n<p>Eine package.json Datei l\u00e4sst sich einfach mit npm init anlegen:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">npm init -y<\/code><\/pre>\n<p>Durch den Schalter <strong>-y<\/strong> \u00fcberspringen wir die zahlreichen Nachfragen nach diversen Metadaten. Diese sind im Moment nicht wichtig und werden erst relevant, wenn ein NPM-Paket in einer Registry publiziert werden soll. F\u00fcr Bibliotheken ist das Standard, bei Anwendungen eher die Ausnahme. Soll dies sp\u00e4ter dennoch geschehen, k\u00f6nnen die betroffenen Felder einfach h\u00e4ndisch mit einem Texteditor in der erzeugten package.json Date erg\u00e4nzt werden.<\/p>\n<p>Nachdem die Datei angelegt ist, k\u00f6nnen wir TypeScript hinzuf\u00fcgen. Im einfachsten Falle ist auf dem Entwicklungssystem npm installiert, sodass wir einfach folgenden Befehl im gleichen Verzeichnis ausf\u00fchren:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">npm i --save typescript<\/code><\/pre>\n<p>Ist das nicht der Fall, einfach auf npmjs.com nach der aktuellsten stabilen Version des Paketes suchen. Zum Erstellungszeitpunktes dieses Artikels ist das 3.9.6 . Im Bereich <strong>dependencies<\/strong> der package.json folgende Zeile einf\u00fcgen:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">&quot;typescript&quot;: &quot;^3.9.6&quot;<\/code><\/pre>\n<p>Dadurch wird TypeScript in der angegebenen Version installiert, wenn wir sp\u00e4ter innerhalb unseres Containers <strong>npm install<\/strong> zur Aufl\u00f6sung der Abh\u00e4ngigkeiten ausf\u00fchren.<\/p>\n<h2 class=\"wp-block-heading\">Dockerfile f\u00fcr TypeScript\/NodeJS<\/h2>\n<p>Das Herzst\u00fcck unseres Containers ist das selbst erstellte <strong>Dockerfile<\/strong>. Es erzeugt die Umgebung, installiert alle notwendigen Abh\u00e4ngigkeiten und richtet diese passend zu unserer Anwendung ein. Im <strong>src<\/strong> Ordner \u00f6ffnen wir dazu die gleichnamige Datei ohne Endung und f\u00fcgen folgendes ein:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">FROM node:lts-alpine\nWORKDIR \/app\nCOPY package.json .\nRUN npm install\n\nCOPY src .\nRUN npx tsc index.ts\nENTRYPOINT [&quot;node&quot;, &quot;index.js&quot;]<\/code><\/pre>\n<p><strong>Zeile 1 <\/strong>legt das offizielle NodeJS-Image als Basis fest. Die LTS-Version ist stabil und erh\u00e4lt am l\u00e4ngsten Updates. Wer m\u00f6chte, kann alternativ auch nat\u00fcrlich auch die neuste stabile Version einsetzen. Gerade f\u00fcr ein RPI-Projekt w\u00fcrde ich jedoch besser zur LTS-Version raten, sofern man nicht zwingend die dort fehlenden brandneuen Funktionen ben\u00f6tigt. Oft ist dies nicht der Fall.<\/p>\n<p>Dar\u00fcber hinaus verwende ich nicht das auf Debian bzw. Raspbian aufbauende Image, sondern Alpine. Dabei handelt es sich um ein minimalistisches Linux. Es ist mit wenigen MB deutlich schlanker als die anderen Distributionen. Das spart Speicher, verk\u00fcrzt Ladezeiten und reduziert auch die potenzielle Angriffsfl\u00e4che. Auch hier w\u00fcrde ich Alpine dem Vorzug geben, sofern nicht aufgrund von Abh\u00e4ngigkeiten explizit Debian\/Raspbian ben\u00f6tigt wird.<\/p>\n<p>In <strong>Zeile 2<\/strong> definieren wir das aktuelle Arbeitsverzeichnis im Container &#8211; vergleichbar mit dem cd Verzeichniswechsel. Der Ordner <strong>\/app<\/strong> hat sich als Quasi-Standard etabliert. Grunds\u00e4tzlich kann man den Pfad frei w\u00e4hlen. Er beeinflusst den Host nicht, da er nur innerhalb des Images und damit dem Container zur Verf\u00fcgung steht.<\/p>\n<p><strong>Zeile 3<\/strong> kopiert die zuvor angelegte package.json Datei mit den Abh\u00e4ngigkeiten, w\u00e4hrend <strong>Zeile 4<\/strong> diese installiert. Das geschieht bewusst getrennt, weil Docker in mehreren Schichten arbeitet. W\u00fcrden wir es uns einfacher machen und den gesamten Ordner mit <strong>COPY . .<\/strong> kopieren, funktioniert das zwar. H\u00e4tte allerdings folgenden Seiteneffekt: Sobald eine Quellcode-Datei ge\u00e4ndert wird, m\u00fcssten auch die NPM-Pakete neu installiert werden &#8211; obwohl die package.json Datei unver\u00e4ndert ist. Als Folge erh\u00f6ht sich die Erstellungszeit deutlich. Den gesamten src-Ordner kopieren wir daher erst im Anschluss. <\/p>\n<p><strong>In der vorletzten Zeile<\/strong> wird der TypeScript-Kompiler ausgef\u00fchrt. Er erzeugt aus <strong>index.ts<\/strong> eine JavaScript-Datei, die von NodeJS gestartet werden kann. <strong>NPX <\/strong>ist ein praktisches Werkzeug, um CLI-Pakete aus dem lokalen <strong>node_modules<\/strong> Ordner auszuf\u00fchren. Es erspart uns die h\u00e4ndische Suche im gleichnamigen Ordner. Au\u00dferdem kann es als Fallback auf global installierte Pakete zur\u00fcckgreifen, falls das gew\u00fcnschte nicht lokal verf\u00fcgbar ist.<\/p>\n<p><strong>Die letzte Zeile<\/strong> legt fest, welcher Befehl beim Start des Containers ausgef\u00fchrt werden soll. Standardm\u00e4\u00dfig erzeugt der TypeScript-Kompiler gleichnamige Dateien, die auf .js statt .ts enden.<\/p>\n<h2 class=\"wp-block-heading\">Image erzeugen und Container starten<\/h2>\n<p>Mithilfe des Dockerfiles k\u00f6nnen wir nun ein Image bauen. Dies enth\u00e4lt alle Abh\u00e4ngigkeiten und Dateien, so wie von uns definiert. Es kann daher zum Starten eines (oder auch mehrerer identischer) Container verwendet werden. Hierzu f\u00fchren wir folgenden Befehl aus:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\" data-line=\"\">docker-compose up --build<\/code><\/pre>\n<p>Es dauert einen Moment, bis alle Schichten erstellt und der Container gestartet wurde. Am Ende sollte man folgende Ausgabe sehen k\u00f6nnen:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">Attaching to test_app_1\ntest_app_1     | Hello World\ntest_app_1 exited with code 0<\/code><\/pre>\n<p>An diesem Punkt wurde unsere index.ts Datei ausgef\u00fchrt. Das anschlie\u00dfende Beenden des Containers ist kein Fehler: Unser Skript erh\u00e4lt keinerlei weitere Anweisungen. Oft startet man hier einen l\u00e4nger laufenden Dienst, wie etwa einen Webserver. Im Gegensatz zu einer VM l\u00e4uft der Container nicht weiter, wenn es nichts zutun gibt. In dem Fall ist das kein Problem, weil es so beabsichtigt ist &#8211; der Container soll nur die Ausgabe t\u00e4tigen, nicht mehr und nicht weniger.<\/p>\n<p>Problematisch ist dies erst, wenn der Container einen lang laufenden Prozess starten sollte. Ein Datenbank-Container d\u00fcrfte sich beispielsweise nicht beenden, sondern muss dauerhaft laufen. Beendet er sich dennoch, besteht ein Konfigurationsfehler.  <\/p>\n<h2 class=\"wp-block-heading\">Weitere Schritte und Verbesserungen<\/h2>\n<p>Nun haben wir eine einfache TypeScript-Anwendung containerisiert auf dem Raspberry Pi gestartet. Dies ist ein praktisches Beispiel f\u00fcr den Einstieg in Docker auf einem Einplatinencomputer. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Docker sowie Docker-Compose sind auch f\u00fcr die ARM-Architektur und damit den Raspberry Pi verf\u00fcgbar. Meist werden sie zwar auf x86-Hardware eingesetzt. Doch auch auf dem Raspberry hat man zunehmend mehrere Anwendungen im Einsatz: Beispielsweise einen Anwendungs- oder API-Server, der in vielen F\u00e4llen nicht ohne Datenbank auskommt. Nicht selten m\u00f6chte man diese Daten auch noch grafisch &#8230;<\/p>\n","protected":false},"author":5,"featured_media":6855,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[671],"tags":[167,817,639,816],"class_list":["post-6852","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-raspberry-pi","tag-javascript","tag-nodejs","tag-raspberry-pi","tag-typescript"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/6852","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=6852"}],"version-history":[{"count":3,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/6852\/revisions"}],"predecessor-version":[{"id":6856,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/6852\/revisions\/6856"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/6855"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=6852"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=6852"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=6852"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}