Anleitung App Entwicklung

Aus ScaleIT Industrie 4.0 Wiki

Das Ziel dieser Anleitung ist, eine ScaleIT-App zu entwickeln:

  • Die App soll ein "Hello ScaleIT" als Ausgabe liefern
  • Aufrufbar mit Web-Browser
  • Die App soll heißen: "helloscaleit"
  • Sie soll eine vollwertige ScaleIT App sein
  • Sie soll aus dem App-Pool installierbar sein

Wenn die App dann erstellt wurde, sind folgende weiterführende möglicherweise Themen relevant:

Zurück zur App-Entwicklung. Folgende Schritte sind dazu erforderlich:


Schritt 1: Die Vorbereitung

Das Handwerkszeug des App-Entwicklers ist:

  • Linux als Betriebssystem
  • Installierte Software: Docker, Docker-Compose, Make
  • Als Editor empfehlen wir: Microsoft VisualCode

Die App-Entwicklung findet statt im Verzeichnis:

   /home/meinname/de-ondics-helloscaleit

Wir legen darin eine Verzeichnisstruktur an wie unter Verzeichnisstrukur_einer_ScaleIT_App bschrieben.

Schritt 2: Unser Programm

Das eigentliche Programm soll hier als Beispiel ein kleines NodeJS-Programm (server.js) sein:

   'use strict';
   const express = require('express');
   // Netzwerk-Erreichbarkeit
   const PORT = 8080;
   const HOST = '0.0.0.0';
   // Hier eine Konstante, die per Environment-Variable 
   // in docker-compose.yml geändert werden kann
   const TEXT = process.env.TEXT ? process.env.TEXT : "Hello ScaleIT";
   // Webserver
   const app = express();
   app.get('/', (req, res) => {
       res.send(TEXT+'\n');
   });
   app.listen(PORT, HOST);
   console.log(`ScaleIT-App läuft unter http://${HOST}:${PORT}`);

Dazu brauchen wir bei NodeJS üblich eine Datei package.json:

   {
       "name": "scaleit-app-helloscaleit",
       "version": "1.0.0",
       "description": "Erste ScaleIT-App in NodeJS",
       "author": "Ondics <info@ondics.de>",
       "main": "server.js",
       "scripts": {
           "start": "node server.js"
       },
       "dependencies": {
           "express": "^4.16.1"
       }
   }

Fertig. Die könnten wir nun schon testen, aber dazu brauchen wir die NodeJS-Laufzeitumgebung und die entsprechenden Bibliotheken (express,...). Die wollen wir nicht auf dem Linux-System installieren, sondern nur im Docker-Container vorhalten.

Schritt 3: Die Containerisierung

Nun muss unser programm in einen Docker-Container verpackt werden.

Dazu ist ein Dockerfile notwendig, das beschreibt, wie der Container gebaut werden soll:

   FROM node:10-alpine
   # Hier ist unser Programm-Verzeichnis im Container
   WORKDIR /usr/src/app
   
   # Wir brauchen die NodeJS-Bibliotheks-Anforderungen
   COPY package*.json ./
   RUN npm install
   # Für Produktionszwecke können die devDependencies wegbleiben
   # RUN npm ci --only=production
   
   # Jetzt muss der Programm-Code noch in den Container
   # wir kopieren am einfachsten das ganze Verzeichnis
   COPY . .
   
   # Der Port 8080 soll im Container von außen aufrufbar sein
   EXPOSE 8080
   # Am Schluss folgt der eigentliche Programmstart
   CMD [ "node", "server.js" ]

Hin paar Hinweise zur Wahl des Basis-Docke-Images, auf dem unser Dockerfile aufbaut:

  • Es kommt von https://hub.docker.com und ist frei verfügbar
  • Wir nutzen Node 10, das reicht
  • Das Image bau auf Alpine auf und ist sehr klein (24 MB), das ist gut für ScaleIT, sonst müssen Anwender ewig bei der App-Installation warten

Wir können nun den Container damit komplett erstellen, ausführen und wieder löschen:

   $ docker build -t ondics/hello-scaleit-app .
   $ docker images
   $ docker run -p 49160:8080 -d clauss/node-web-app
   $ docker ps
   $ docker kill 2cf3bbf3eb2e && docker rm 2cf3bbf3eb2e

Wir haben nun drei Möglichkeiten, den laufenden Container zu testen:

1. Mit dem Browser

Mit Aufruf der Url http://0.0.0.0:49160 sollte unsere App "Hello ScaleIT" ausgeben.

2. Mit der Linux-Kommandozeile (im Terminalfenster):

curl ist ein tolles Tool, um Webseiten aufzurufen:

   $ curl http://0.0.0.0:49160
   Hello ScaleIT

3. Im Container selbst

Wir gehen zuerst in den Container hinein und rufen dann mit curl die Seite auf:

   $ docker exec -it 2cf3bbf3eb2e /bin/sh
   # wget -O - -q http://0.0.0.0:8080
   Hello world
   # exit

Zum Schluss vernichten wir den Container wieder und löschen das Image.

   $ docker kill 2cf3bbf3eb2e && docker rm 2cf3bbf3eb2e && docker rmi ...

Schritt 4: Die Container-Automation

Die Docker-Befehle sind recht kompliziert, deswegen wurde Docker-Compose erfunden.

Wir bauen uns also eine Docker-Compose-Beschreibungsdatei docker-compose.yml:

   version: "3.5"
   services:
     app:
       # Das Dockerfile im aktuellen Verzeichnis benutzen
       build: .
       # Mit den Env-Variablen wird unser programm parametrisiert
       environment:
         - TEXT=Hello ScaleIT, beschtes für Industre 4.0
       # Der Port 8080 wird an den Port 49160 des PC's gebunden
       # und ist damit aufrufbar aus dem Browser, für curl, etc.
       ports:
         - 49160:8080

Fertig.

Damit kann man den Container noch leichter bauen und unser Programm noch leichter starten:

   $ docker-compose build
   $ docker-compose up –d
   $ docker-compose ps
   $ docker logs -f
   $ docker-compose exec app /bin/sh
   # wget -O - -q http://0.0.0.0:8080
   Hello ScaleIT, beschtes für Industre 4.0
   # exit
   $ curl http://0.0.0.0:49160
   Hello ScaleIT, beschtes für Industre 4.0
   $ docker-compose down

Der letzte Befehl räumt alles auf, was mit dem Container zu tun hat.

Schritt 5: Die Build-Automation

Wir machen es uns noch leichter: Das uralte Unix-Werkzeug "make" muss dafür herhalten.

Hier ist ein kleines Makefile, das den Alltag des Entwicklers sehr erleichtert:

   .PHONY: help build
    
    help:
	@echo "# ScaleIT App-make-Helferlein"
	@echo "# (C) Ondics, 2019"
	@echo Befehle: make ...
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
    
    .DEFAULT_GOAL := help
    
    up: ## alle Services starten
	docker-compose up -d
    
    down: ## alle Services stoppen
	docker-compose down --remove-orphans
	
    build: ## alle Container bauen
	docker-compose build

    ps: ## was läuft?
	docker-compose ps

    logs: ## Alle Logs aller Container zeigen
	docker-compose logs -f -t
   

Der Aufruf der make-Befehle ist supereinfach:

Hilfe anzeigen:

   $ make

Befehle ausführen, z.B.:

   $ make build

Befehle können sogar hintereinander ausgeführt werden:

   $ make build up logs

Einfacher geht's nicht!

Schritt 6: Das Rancher-Template

Mit dem Rancher-Template wird festgelegt, wie ein App gestartet wird, dazu zählen:

  • Name und Versionsnummer der App (Datei config.yml)
  • App-Icon (Datei catalogIcon-de-ondics-pacman.png)
  • Informationen zur App und zur Version (Versionsdatei 0/README.md)
  • Aufbau des Installations-Formulars (Versionsdatei 0/rancher-compose.yml)
  • Docker-Compose zum Start der Container (Versionsdatei 0/docker-compose.yml.tpl)

Die Dateien im Verzeichnis 0/... sind Versionsdateien. Pro installierbarer Version wird ein neues Verzeichnis angelegt mit aufsteigender Nummerierung.

Viele Beispiele von Rancher-Templates sind verfügbar unter https://github.com/rancher/community-catalog/tree/master/templates

Datei config.yml

Beispiel:

name: ScaleIT-Pacman
description: |
  ScaleIT Pacman - Das Spiel
maintainer: Ondics GmbH
license: "Ondics ScaleIT App License"
version: 1.10.2
category: Spiel

Hinweise:

  • Die version muss exakt mit der Version im jeweiligen docker-compose.yml.tpl übereinstimmen
  • Als Maintainer wird im Rancher immer "Community Support" dargestellt. Das kann aktuell nicht geändert werden

Datei rancher-compose.yml

Beispiel:

.catalog:
  name: ScaleIT Pacman
  version: 1.10.2
  description: "ScaleIT Datenfluss-Steuerung, App-Chains und Dashboards mit NodeRED"
  minimum_rancher_version: v1.6.0
  questions:
    - variable: ssoproxy
      description: |
        Mit dem SSO-Proxy ist https und die Namensauflösung aktiviert.
        Der DNS muss korrekt eingerichtet sein.
      label: "ScaleIT SSO-Proxy benutzen?"
      required: true
      default: false
      type: boolean
  ...


Hinweise:

Datei docker-compose.yml.tpl

Beispiel:

version: '2'
services:
  de-ondics-pacman-app:
    image: registry.app-pool.scaleit-i40.de/r16/enterprise/de-ondics-pacman-app:1.9
    restart: always
    labels:
      rap.host: ${DOMAINPREFIX}
      rap.sso_disabled: "true"
{{- if eq .Values.ssoproxy "false"}}
    ports:
      - "${APP_DOMAIN_PORT}:80"
{{- end}}

  de-ondics-pacman-registration:
    image: registry.app-pool.scaleit-i40.de/r16/enterprise/de-ondics-pacman-registration:1.3
    restart: always
    environment:
      - APP_ID=de-ondics-pacman
      ...


Hinweise:

  • Die Datei-Ending .tpl aktiviert die Variablen-Ersetzung von {{...]] in Rancher, z.B. if-then-Konstrukte
  • Die Version muss 2 sein
  • Die Angaben unter labels: sind für die Namensadressierung in der EE-Version erforderlich
  • Die Angaben unter ports: sind für die Adressierung des Containers in der CE-Version erforderlich

Schritt 7: Das Registration Sidecar

Das Registration Sidecar ist teil der App. Es läuft in einem eigenen Container und hat folgende Aufgaben:

  • Registrierung der App im ScaleIT App-Verzeichnis (ETCD)
  • Entfernung der Registrierung, wenn die App gestoppt wird

Was wird alles über die App im App-Verzeichnis eingetragen?

  • App-Name
  • ScaleIT App-ID
  • Url des App-Icons
  • Beschreibung / Kurzbeschreibung der App
  • Url zum App-Start
  • Url zur App-API
  • Url zur App-Administration
  • Infos zur Rancher-Umgebung (Host-IP, Nutzung von SSO, Stackname)

Das Registration Sidecar kann Out-Of-The-Box genutzt werden. Es ist verfügbar unter https://github.com/scaleit-i40/registration

Hier ein Beispiel, wie es im Rancher-Template (docker-compose.yml.tpl) der App eingebaut werden kann:

  services:
    # Domain-Container mit der App:
    de-ondics-pacman-app: 
      image: . . .

    # Registration sidecar
    de-ondics-pacman-registration:
      image: registry.app-pool.scaleit-i40.de/ondics/enterprise/de-ondics-pacman-registration:1.1
      restart: always
      environment:
        - APP_ID=de-ondics-pacman
        - APP_NAME=ScaleIT Pacman
        - APP_TITLE=ScaleIT Pacman
        - APP_SHORT_DESCRIPTION=ScaleIT Pacman
        - APP_DESCRIPTION=ScaleIT Pacman Web-Game
        - APP_DOMAIN_DESCRIPTION=ScaleIT Pacman
        - APP_CATEGORY=domainApp
        - APP_DOMAIN_PORT=${APP_DOMAIN_PORT}
        - APP_DOMAIN_PATH=/
        - APP_DOMAIN_SERVICENAME=
        - APP_ICON_PORT=${APP_SIDECAR_WEBCONTENT_PORT}
        - APP_ICON_PATH=/icon.png
        - APP_ICON_SERVICENAME=webcontent
        - APP_API_PORT=${APP_SIDECAR_WEBCONTENT_PORT}
        - APP_API_PATH=/
        - APP_API_SERVICENAME=webcontent
        - APP_ADMIN_PORT=${APP_DOMAIN_PORT}
        - APP_ADMIN_PATH=/
        - APP_ADMIN_SERVICENAME=
        - HOST_IP=${HOST_IPADDR}
        - SSO_ACTIVATED=${ssoproxy}
        - SSO_APP_PREFIX=${DOMAINPREFIX}
        - STACK_NAME={{ .Stack.Name }}

Hinweise:

  • Der Image-Name des Registration Sidecars muss der gleiche sein, der auch in den App-Pool gepuusht wurde.
  • Die Rancher-Variablen Vorlage:... müssen als Rancher-Questions abgefragt werden.
  • Die Variable Vorlage:.Stack.Name ist eine von Rancher vordefinierte Variable und enthält den vom Benutzer bei der Installation vergebenen Stacknamen

Die Rancher-Questions für das Registration Sidecar können so spezifiziert werden:

  questions:
    - variable: ssoproxy
      description: |
        Mit dem SSO-Proxy ist https und die Namensauflösung aktiviert.
        Der DNS muss korrekt eingerichtet sein.
      label: "ScaleIT SSO-Proxy benutzen?"
      required: true
      default: false
      type: boolean
    - variable: DOMAINPREFIX
      description: "Name für die App (für die URL)"
      label: "Name (für URL)"
      type: string
      required: true
      default: "pacman"
    - variable: HOST_IPADDR
      description: "Host-TCP/IP-Adresse, kann auch Domainname dieser ScaleIT-Instanz sein"
      label: "IP-Adresse dieser ScaleIT-Instanz (für ETCD,...)"
      type: string
      required: true
    - variable: APP_DOMAIN_PORT
      description: "Port dieser App (Voreinstellung: 51505)"
      label: "TCP-Port für diese App"
      type: int
      required: true
      default: 51505
    - variable: APP_SIDECAR_WEBCONTENT_PORT
      description: "TCP-Port für statischen Content dieser App (Voreinstellung: 51506)"
      label: "TCP-Port für statischen Content dieser App"
      type: int
      required: true
      default: 51506

Hinweise dazu:

  • Die Ports sind nur relevant, wenn SSO nicht verwendet wird, z.B. in der CE-Version. Sie sind trotzdem immer anzugeben, da eine App sowohl in der CE als auch in der EE Version laufen muss.
  • Die Port-Nummern sind so zu vergeben, dass es keinen Konflikt mit anderen Apps gibt. Sonst kann eine App nicht korrekt installiert werden.
  • Wenn die App in offizielle App-Pools aufgenommen werden soll, müssen die Port-Nummern vom App-Pool-Betreiber vergeben werden


Schritt 8: Veröffentlichung im App-Pool

Eine App kann in Rancher installiert werden, wenn

  • das Rancher-Template der app in einem Git-Server zur Verfügung steht
  • die Images aus einer Docker-Registry geladen werden können

Genau dafür gibt es den ScaleIT-App-Pool. Er umfasst beide Komponenten.

Es gibt folgende Arten von App-Pools:

  • Lokaler App-Pool (CE): Er ist in jeder Community-Edition ScaleIT-Installation enthalten
  • Lokaler App-Pool (EE): Er ist in jeder Enterprise-Installation als eigene App enthalten. Damit sind zusätzlich App-Exporte und App-Importe möglich
  • Community App-Pool: Dieser ist in jeder CE-ScaleIT-Instanz konfiguriert
  • Enterprise App-Pool: Dieser ist in jeder EE-ScaleIT-Instanz konfiguriert

Das nachfolgende Beispiel (Pacman-App) zeigt, wie eine App in den lokalen App-Pool (CE oder EE) gebracht wird. Hierzu erforderlich ist folgendes:

  • Eine Datei /docker-compose.yml im Root-Verzeichnis der App
  • Ein Rancher-Template in der App uunter /Resources/App-Pools/Rancher1.6/de-ondics-pacman
  • Zugangsdaten zum lokalen App-Pool (Git und Docker Regisiry)

Datei /docker-compose.yml

Beispiel:

version: '2'
services:
  de-ondics-pacman-app:
    build: DomainSoftware/pacman
    image: registry.app-pool.scaleit-i40.de/r16/enterprise/de-ondics-pacman-app:1.9
  
  de-ondics-pacman-registration:
    build: PlatformSidecars/registration
    image: registry.app-pool.scaleit-i40.de/r16/enterprise/de-ondics-pacman-registration:1.3

Hinweise:

  • Mit build: DomainSoftware/pacman wird das Domain-Image der App erstellt. Hierzu muss im Verzeichnis /DomainSoftware/pacman ein entsprechendes Dockerfile liegen
  • Mit image: wird das erstellte Image so benannt, dass es im Rancher wieder geladen werden kann (es bekommt die Versionsnummer als Tag)
  • Environment-Variablen, Volumes, etc. sind nicht erforderlich, weil diese Images nicht lokal ausgeführt werden sollen, sondern erst durch Rancher (und dort gibt es dazu ein docker-compose.yml.tpl

Die Images werden dann erstellt und in die Docker Registry gebracht:

$ docker-compose build
$ docker-compose push

Rancher-Templates in App-Pool übertragen

Wenn die Rancher-Templates erstellt sind (Schritt 6), müssen sie in den App-Pool (Git-Server) übertragen bzw. dort aktualisiert werden.

Dieses sollte außerhalb des App-Verzeichnisses gemacht werden.

Beispiel für die erste App-Veröffentlichung:

$ cd /tmp
$ git clone http://lokaler-app-pool.scaleit:1234/rancher-templates.git rancher-templates
$ cd rancher-templates
$ cp -rp /home/.../de-ondics-pacman .
$ git config user.name myuser
$ git config user.email myemail@ondics.de
$ git add .
$ git commit -m 'neue app de-ondics-pacman' -a
$ git push origin master

Beispiel für App-Updates:

$ cd /tmp
$ cd rancher-templates
$ git pull origin master
$ cp -rp /home/.../de-ondics-pacman .
$ git add .
$ git commit -m 'neue version von de-ondics-pacman' -a
$ git push origin master

Hinweise:

  • Die Zugangsdaten zum lokalen App-Pool stehen im Systemhandbuch der ScaleIT-Instanz
  • Wenn eine Version gelöscht werden soll, muss das entsprechende Versions-Verzeichnis gelöscht werden
  • Mit einem Makefile könnten diese Schritte einfacher ausgeführt werden

Schritt 9: Installation der App in Rancher

Folgende Schritte sind dazu erforderlich:

  1. Im Rancher-menü unter "Catalog" wird zunächst der lokale App-Pool ausgewählt
  2. Mit dem Button "Aktualisierung" (rechts oben) die neuesten Rancher-Templates aus dem Git-Server laden. Dann muss die neue/aktualisierte App angezeigt werden
  3. Die App auswählen
  4. Das Formular komplett ausfüllen. Die Daten für docker-compose.yml.tpl und rancher-compose.ymlkönnen unten eingesehen werden (aufklappen)
  5. Zum Abschluss die App über das Launchpad starten

Typische Fehler:

  • Manifest kann nicht gefunden werden (wird rot angezeigt): Der Image-Namen oder das Image Tag (Versionsnummer) ist vermutlich falsch
  • Die App startet und stoppt gleich wieder bzw. startet erneut: Das deutet auf einen Fehler im Dockerfile hin (in Rancher die Container-Logfiles anschauen)
  • Die App kann per Url nicht aufgerufen werden: Stimmen die Rancher-Labels?
  • Die App wird im Launchpad nicht angezeigt: Das Registration Sidecar funktioniert nicht richtig. Die Environment-Variablen müssen ggf. korrigiert werden

Zum Debugging eines einzelnen Containers kann dieser im Stack geöffent werden und dort die Logs angezeigt werden.

Tipp bei App-Updates:

Wenn nur der DomainContainer aktualisiert wurde, muss kein neues Rancher-Template erstellt werden, sondern es genügt, das Image in den lokalen App-Pool zu übertragen und dann im Rancher einen Container-Update auszuführen. Dann muss bei "Update Service" die Checkbox "Image vor der Erstellung immer laden" ausgewählt werden.







Fragen zur App-Entwicklung? Dafür haben wir ScaleIT I40-Forum erstellt unter https://forum.scaleit-i40.de