GO(lang) on Raspberry Pi


Kleine Anwendungen können ganz gut auch auf kleinen Systemen laufen. Da ich mich in letzter Zeit öfter mit GO (Golang) auseinandersetze, wende ich mich ein wenig von Windows ab und schaue seit längerer Zeit mal wieder über den Tellerrand. Hängen geblieben bin ich in den verfügbaren Linux Betriebssysteme für Raspberry PI und dem Einsatz von Docker.

Mit Docker kann man alles machen. Naja, nicht alles alles. Mein Kaffee muss ich immer noch selber kochen, außer ich schreibe eine Anwendung und baue einen Treiber mit Sensoren und Aktoren, um die Kaffeemaschine zu kontrollieren. Dann sollte das auch mit Docker gehen. Oder ich schicke der Frau eine Nachricht und bitte um Kaffeenachschub. Aber fangen wir mit etwas einfacherem an.

Wir brauchen dazu:

Anforderung
Am Ende soll auf dem Raspberry Pi ein Webservice laufen, der in einem Docker Container gestartet wird. Der Dienst soll von einem anderen PC mit einem Browser im lokalen Netzwerk aufrufbar sein und schließlich eine Testwebseite wiedergeben.

Webservice entwickeln in GO
Einen Webservice in GO zu schreiben ist sehr übersichtlich. In C# mit .NET Framework habe ich deutlich mehr Codes geschrieben, bis endlich ein Webservice lief. Zudem fällt mir das Wegdenken von Programmiergewohnheiten schwer, die eigentlich nicht sein müssen. Man spricht auch oft von Boilerplate-Code.

Kommen wir zu dem GO Code mit dem geschriebenen Webservice. Nachdem wir im Projektordner eine neue Datei angelegt haben mit dem Namen 'main.go', geht das Schreiben mit dem Code los, bzw. einfach den folgenden Code nach dem Absatz kopieren.
Im Detail zu der Sprache GO kann ich die Seite von Golang 'A Tour of Go' empfehlen. Dort lernt ihr alle Grundlagen zu der Sprache und könnt die Eingaben direkt im Browser prüfen.
In der 'main()'-Funktion startet die Anwendung den Webservice. Kommt ein URL Request rein, wird durch die Handler Function ‚http.handleFunc‘ der Delegat ‚webserviceHandler‘ ausgeführt. Dieser kann den eingehenden Request verarbeiten. Doch für dieses Beispiel kommen wir ohne Parameter aus. Stattdessen wird eine HTML Seite eingelesen und als Response zurückgegeben.

 package main  
 import (  
      "fmt"  
      "io/ioutil"  
      "net/http"  
 )  
 func main() {  
      fmt.Println("start webservice")  
      http.HandleFunc("/", webserviceHandler)  
      http.ListenAndServe(":5000", nil)  
 }  
 func webserviceHandler(w http.ResponseWriter, r *http.Request) {  
      webside, err := ioutil.ReadFile("index.html")  
      if err != nil {  
           fmt.Println(err)  
           return  
      }  
      fmt.Fprintf(w, string(webside))  
 } 




Lokal den Service ausprobieren
An der Stelle sollte der Code bereits ausführbar sein. Wenn jetzt F5 zum Ausführen gedrückt wird, dann sollte Lokal mit einem Browser der Webservice mit der Adresse 'http://localhost:5000' aufrufbar und das ‚Hello World Webseite‘ zu lesen sein.

Dockerfile anlegen
Damit der Webservice auch im Docker Container funktioniert, ist relativ wenig zu tun. Relativ, an der Stelle habe ich lange gesucht und mich lange mit Docker auseinandergesetzt, um am Ende die einfach aussehende Dockerfile zu schreiben. An der Stelle kann ich nur empfehlen, die einzelnen Befehle nach zu lesen, die für die Dockerfile verwendet werden können. Dockerfile Preference https://docs.docker.com/engine/reference/builder/
Die Dockerfile kommt in dasselbe Verzeichnis, wie die Datei mit dem GO Code. Die Einträge mit 'LABEL' sind optional und können auch weggelassen werden.

 # Initialisiert die Bereitstellung und   
 # legt das Basis Image mit nachfolgenden Anweisungen an  
 FROM golang:onbuild  
 # label of maintainer  
 LABEL maintainer="Codexzier"  
 LABEL version="1.0"  
 LABEL description="A mini messenger server with based user."  
 # legt das Verzeichnis an für die Ausführung  
 WORKDIR /app  
 # kopiert die neuen dateien in den container  
 COPY . .  
 # Führt das Image aus und übergibt das Ergebnis für die Zielumgebung  
 RUN env GOOS=linux GOARCH=arm GOARM=5 go build -o main .  
 # Freigabe der Portnummer auf das der docker container zuhört  
 EXPOSE 8002  
 # Anweisung für den Ausführenden Container  
 CMD ["./main"]  

Docker Image erstellen
Ab dieser Stelle sollte Docker installiert und gestartet sein. Ihr solltet auch einen Account haben, damit das Image anschließend auf Dockerhub geladen werden kann.
In Visual Studio Code öffnen wir zunächst ein Terminal. Hier sollte aktuell das Verzeichnis aufgerufen sein, worin sich der Programmcode, Webseite und die Dockerfile befinden. Hier gibt ihr folgenden Command ein und am Ende den Punkt nicht vergessen:

docker build -t <image name> .




Nun sollte eine Folge von Arbeitsschritten erfolgen bis dann am Ende Successfully build mit einer kryptischen ID steht und ggf. mit einigen Warnhinweisen.





Bevor auf Dockerhub geladen werden kann, muss noch das Image tagged gesetzt werden (Image tagged, da bin ich nicht sicher ob man das so beschreiben kann). Statt wie bei mir mit Codexzier, müsst ihr euren Docker Username verwenden.

docker tag <tag name>:latest <docker username>/<ziel name>:latest





Nun kann das Image auf Dockerhub hochgeladen werden. Auch hier verwendet ihr euren Username von Docker.

docker push < docker username >/< ziel name >:latest




Nach ein paar Sekunden sollte der Upload abgeschlossen sein. Wenn ihr euch auf Dockerhub https://hub.docker.com/ eingeloggt habt, sollte euer Image in euren Repositories zu finden sein.




Das Image steht bereit und kann vom Zielsystem abgeholt werden. Auf Dockerhub könnt ihr weitere Informationen zu eurem Image hinterlegen. Zum Beispiel, was der Inhalt ist und welche Aufgabe die Anwendung erfüllt. Wenn ihr wollt, dass nur ihr das Image sieht, dann kann hier auch auf Private umgestellt werden. Interessant dürfte auch das Automated Builds sein, aber das wäre wieder ein eigenes Thema.

Docker auf dem Raspberry Pi
Das Beispiel zeigt den Einsatz von Docker mit dem Betriebssystem Raspbian. Von Haus aus ist Docker nicht installiert, ebenso wenig wie Docker-Compose. Im Netz finden sich einige Beschreibungen, daher habe ich den Part hier ausgelassen.

Hinweis: Bei mir muss ich kein "sudo" vor jeden command eingeben.

Um nun das Image herunter zu laden, muss folgender Command ausgeführt werden:

docker pull <docker username>/<image name>




Nach dem Download wird das Image mit einer Image-ID angelegt. Die brauchen wir, um die Anwendung zu starten. Mit folgendem Befehl bekommt ihr alle vorhandenen Images ausgeben.

docker image ls

In der Spalte zu IMAGE ID steht, die ID zu eurem heruntergeladenen Image. Die hier angezeigte ID wird Lokal erzeugt und sollte eine andere ID abbilden.
Mit dem nächsten Command, setzt ihr die entsprechende ID am Ende ein:

docker run --publish < port number to pulish >:< internal port number > -t < image id >



Die gewählte Port 8003 im Beispiel kann frei gewählt werden, wenn die Portnummer nicht von einer anderen Anwendung bereits verwendet wird. Die IP Adresse von Raspberry könnte ihr unter Raspbian auslesen, in dem ihr mit der Maus über das Netzwerk Symbol fährt. Mit der von eurem Raspberry Pi IP Adresse und der gesetzten Portnummer, solltet ihr auf eurem PC, Notebook und Smartphone auf den Raspberry zugreifen können und die Webseite 'Hello World!' erhalten.


Langer Weg für die Bereitstellung
Die Bereitstellung ist etwas lang, aber durch die Docker-Umgebung auf eine einheitliche Ebene gebracht. Das Gleiche sollte nicht nur mit GO funktionieren, sondern auch mit anderen Sprachen. Und Dank Docker können wir einen Service schreiben, der praktisch auf allen Systemen bereitgestellt werden kann.

Wie immer ist der fertige Code auf meinem Repository verfügbar

Kommentare

Beliebte Posts aus diesem Blog

Arduino Control (Teil 5) - PWM Signal einlesen

RC Fahrtenregler für Lego Kettenfahrzeug

Angular auf dem Raspberry Pi