Lektion 1 von 8
Die erste App in der Cloud: PaaS, Region und Logs
Du wählst zwischen App Service, Container Apps und Functions, deployst eine Node.js-App mit der Azure CLI in Switzerland North und findest einen Startfehler im Log Stream.
Worum es geht
Es ist 11.15 Uhr in Buchs SG. In drei Firmenkantinen bestellen gerade über 400 Mitarbeitende ihr Mittagsmenü über die App, die das Team der Rheintal Apps GmbH für ein Cateringunternehmen gebaut hat. Die App läuft auf einem einzigen Server im Büro. Wenn jetzt der Strom ausfällt, ein Windows-Update neu startet oder die Festplatte voll ist, gibt es in drei Kantinen kein Mittagessen. Und wer patcht diesen Server eigentlich am Wochenende?
Sara, Lernende im 2. Lehrjahr Applikationsentwicklung, bekommt den Auftrag: «Bring die Mittagsmenü-App in die Cloud. Und zwar so, dass wir uns um den Code kümmern und nicht um Betriebssysteme.» Genau dafür gibt es PaaS (Platform as a Service): Du lieferst deinen Code oder dein Container-Image, die Plattform kümmert sich um Server, Updates, HTTPS, Neustarts und Skalierung.
In dieser Lektion bringst du eine Node.js-App mit der Azure CLI auf Azure App Service in der Region Switzerland North. Du lernst, wie die Plattform deine App startet, warum der allererste Versuch oft mit einem Fehler 503 endet und wie du die Ursache in den Logs in zwei Minuten findest statt in zwei Stunden. Die Grundbegriffe IaaS, PaaS und SaaS kennst du aus Modul 346. Hier geht es um die Praxis: Befehle, Konfiguration, Fehlersuche.
PaaS im Alltag: die Mietküche
Stell dir vor, das Cateringunternehmen bräuchte für einen Grossanlass eine zweite Küche. Es könnte selbst eine bauen: Raum mieten, Herde kaufen, Lüftung installieren, Hygienekontrolle organisieren. Oder es mietet eine Gastro-Mietküche: Die Herde stehen bereit, jemand anderes wartet die Lüftung und putzt die Fettabscheider. Das Catering bringt nur noch Rezepte, Zutaten und Personal mit.
PaaS ist die Mietküche für Software:
| Aufgabe | Eigener Server im Büro | PaaS (z.B. App Service) |
|---|---|---|
| Hardware, Strom, Kühlung | du | Plattform |
| Betriebssystem installieren und patchen | du | Plattform |
| Node.js-Laufzeit aktualisieren | du | Plattform (du wählst die Version) |
| HTTPS-Zertifikat für die Standardadresse | du | Plattform |
| Neustart nach Absturz | du | Plattform |
| Code, Abhängigkeiten, Konfiguration | du | du |
| Daten und Zugriffsrechte | du | du |
Was du abgibst, ist Kontrolle: Du kannst dich nicht auf «den Server» einloggen und schnell etwas umstellen. Alles, was deine App braucht, bekommt sie über den Code oder über Einstellungen der Plattform. Genau diese Disziplin macht die App später reproduzierbar und skalierbar.
Drei Wege für deine App
Azure bietet für eigene Anwendungen vor allem drei PaaS-Dienste. AWS hat für jeden ein Gegenstück.
| Azure App Service | Azure Container Apps | Azure Functions | |
|---|---|---|---|
| Was du lieferst | Code (Node.js, Python, Java, .NET, PHP) oder Container | Container-Image | Einzelne Funktionen |
| Typisch für | Klassische Web-Apps und APIs | APIs und Microservices aus Docker-Images | Hintergrundaufgaben, Ereignisse |
| Abrechnung | Fester Plan pro Stunde | Pro Rechensekunde, Grundlast möglich | Pro Ausführung und Rechenzeit |
| Scale-to-zero | nein, der Plan läuft immer | ja | ja (Consumption-Pläne) |
| Gegenstück bei AWS | Elastic Beanstalk | ECS mit Fargate | Lambda |
Die Entscheidung hängt vor allem davon ab, was du schon hast: Liegt ein Dockerfile im Repository (Modul 347), ist Container Apps naheliegend. Für ein Node.js-Projekt mit package.json ist App Service der kürzeste Weg. Functions sind für kurze, ereignisgesteuerte Aufgaben (Lektion 6).
Die Mittagsmenü-App hat kein Dockerfile, Sara wählt deshalb App Service. Wie du so eine Wahl mit Kosten und Betriebsaufwand begründest, lernst du in Lektion 8.
Die Auftrags-API eines Velokuriers (Spring Boot) liegt bereits als Docker-Image in einer Registry. Sie soll mit wenig Aufwand als HTTPS-API laufen und nachts, wenn niemand Aufträge erfasst, auf null Instanzen gehen dürfen. Welcher Dienst passt am besten?
Region Schweiz und Kostenbremse
Bevor du die erste Ressource anlegst, klärst du zwei Dinge.
1. Wo liegen die Daten? Die App speichert Namen und Bestellungen von Mitarbeitenden, also Personendaten. Der Kunde verlangt, dass sie in der Schweiz bleiben. Azure hat zwei Schweizer Regionen: Switzerland North (Raum Zürich, CLI-Name switzerlandnorth) und Switzerland West (Raum Genf, switzerlandwest). Switzerland West ist vor allem als Partnerregion für Notfallkopien gedacht und bietet weniger Dienste. Bei AWS heisst die Schweizer Region eu-central-2 (Zürich). Achtung: AWS eu-central-1 liegt in Frankfurt, Azure westeurope in den Niederlanden.
Nicht jeder Dienst und nicht jede Leistungsstufe ist in jeder Region verfügbar. Prüfe das vor der Planung, zum Beispiel mit:
# Alle Regionen deines Abonnements mit Anzeigename
az account list-locations --query "[?contains(name, 'switzerland')].{Name:name, Anzeige:displayName}" --output table2. Was darf es kosten? Für die Ausbildung gibt es Azure for Students mit einem Startguthaben ohne Kreditkarte. Setze trotzdem sofort im Portal unter «Cost Management» ein Budget mit Alarm, zum Beispiel bei CHF 20. Ein vergessener Datenbankserver läuft sonst wochenlang weiter.
Die Bestelldaten der Mittagsmenü-App sind Personendaten und sollen gemäss Kundenvorgabe in der Schweiz gespeichert werden. Welche Regionen erfüllen diese Vorgabe?
Wähle alle zutreffenden Antworten.
Wie die Plattform deine App startet
Damit du Fehler verstehst, musst du wissen, was beim Start passiert. Bei App Service unter Linux mit Code-Deployment läuft vereinfacht Folgendes ab:
- Du lädst deinen Code als ZIP-Datei hoch.
- Der Build-Dienst der Plattform erkennt
package.json, führtnpm installaus und merkt sich den Startbefehl (npm start). - Die Plattform startet deine App in einem Container und setzt dabei die Umgebungsvariable
PORT, typischerweise auf 8080. - Ein vorgeschalteter Proxy nimmt HTTPS-Anfragen auf Port 443 entgegen und leitet sie intern an genau diesen Port weiter.
- Die Plattform sendet Test-Anfragen («HTTP Pings») an deine App. Antwortet sie innerhalb der Wartezeit nicht, gilt der Start als fehlgeschlagen und Besucher sehen einen Fehler.
Daraus folgt die wichtigste Regel dieser Lektion: Deine App muss auf dem Port hören, den die Plattform in PORT vorgibt. Lokal darf sie auf 3000 zurückfallen:
// server.js: Mittagsmenü-App (Ausschnitt)
const express = require("express");
const app = express();
app.get("/health", (req, res) => res.json({ status: "ok" }));
app.get("/api/menues", (req, res) => {
res.json([
{ kantine: "Buchs", menue: "Älplermagronen mit Apfelmus", preis: 13.5 },
{ kantine: "Sargans", menue: "Gemüsecurry mit Basmatireis", preis: 12.0 },
]);
});
// PORT kommt von der Plattform, 3000 nur für lokale Tests
const port = Number(process.env.PORT) || 3000;
app.listen(port, () => console.log(`Menü-App hört auf Port ${port}`));Bei Container-Deployments gilt dasselbe Prinzip mit anderem Namen: In App Service für Container teilst du der Plattform mit der App-Einstellung WEBSITES_PORT mit, auf welchem Port dein Container hört. In Container Apps trägst du den Target Port in der Ingress-Konfiguration ein. Ausserdem muss die App in einem Container auf allen Schnittstellen hören (0.0.0.0), nicht nur auf 127.0.0.1.
Nach dem Deployment zeigt App Service nur «Application Error». Im Log Stream steht:
Menü-App hört auf Port 3000
Container app-menueapp-test-chn didn't respond to HTTP pings on port: 8080, failing site start.Im Code steht app.listen(3000). Was ist die richtige Korrektur?
Schritt für Schritt: Die Mittagsmenü-App auf App Service bringen
Ausgangslage: Ein Node.js-Projekt mit package.json (Startskript "start": "node server.js"), lokal getestet. Sara arbeitet in der Azure Cloud Shell oder lokal nach der Installation der Azure CLI.
Schritt 1: Anmelden und Abonnement prüfen.
az login
az account show --query "{Abo:name, Id:id}" --output tableZwischenergebnis: Als Abo erscheint «Azure for Students». Steht dort ein Firmenabo, wechselst du mit az account set --subscription <Id>.
Schritt 2: Namen festlegen und Ressourcengruppe anlegen.
RG="rg-menueapp-test-chn"
PLAN="asp-menueapp-test-chn"
APP="app-menueapp-test-chn" # muss weltweit eindeutig sein
LOC="switzerlandnorth"
az group create --name "$RG" --location "$LOC"Zwischenergebnis: "provisioningState": "Succeeded" und "location": "switzerlandnorth".
Schritt 3: App Service Plan anlegen. Der Plan ist die Rechenleistung, auf der eine oder mehrere Apps laufen. Für den Test reicht die kleine Stufe B1 unter Linux.
az appservice plan create --resource-group "$RG" --name "$PLAN" \
--location "$LOC" --sku B1 --is-linuxSchritt 4: Web-App mit Laufzeit anlegen. Welche Versionen es gibt, zeigt az webapp list-runtimes --os linux.
az webapp create --resource-group "$RG" --plan "$PLAN" --name "$APP" \
--runtime "NODE:22-lts"
az webapp update --resource-group "$RG" --name "$APP" --https-only trueSchritt 5: Build beim Deployment einschalten. Damit die Plattform npm install ausführt:
az webapp config appsettings set --resource-group "$RG" --name "$APP" \
--settings SCM_DO_BUILD_DURING_DEPLOYMENT=true NODE_ENV=productionSchritt 6: Code als ZIP deployen. Den Ordner node_modules packst du nicht ein, die Plattform installiert die Pakete selbst.
zip -r app.zip . -x "node_modules/*" ".git/*"
az webapp deploy --resource-group "$RG" --name "$APP" --src-path app.zip --type zipSchritt 7: Testen und den Fehler finden. Saras erste Version hatte app.listen(3000) fest im Code. Der Browser zeigt nach einigen Minuten nur «Application Error», ein curl liefert Status 503. Jetzt nicht raten, sondern Logs lesen:
az webapp log config --resource-group "$RG" --name "$APP" \
--docker-container-logging filesystem
az webapp log tail --resource-group "$RG" --name "$APP"Im Log Stream steht:
Menü-App hört auf Port 3000
Container app-menueapp-test-chn didn't respond to HTTP pings on port: 8080, failing site start.Die erste Zeile zeigt: Die App ist gestartet, aber auf Port 3000. Die zweite: Die Plattform wartet auf Port 8080. Ursache gefunden.
Schritt 8: Korrigieren, neu deployen, prüfen. Sara ersetzt die feste Zahl durch process.env.PORT, baut das ZIP neu und deployt nochmals.
HOST=$(az webapp show --resource-group "$RG" --name "$APP" --query defaultHostName --output tsv)
curl --silent "https://$HOST/health"Zwischenergebnis: {"status":"ok"}, über HTTPS mit einem Zertifikat, um das sich Sara nie kümmern muss.
Im Code-Labor baust du jetzt einen kleinen Log-Detektiv, der aus einem Log Stream die Ursache eines Startfehlers herausliest.
Code-Labor: Log-Detektiv für Startfehler
Schreibe diagnose(log). log ist der Text aus dem Log Stream (mehrere Zeilen). Die Funktion gibt die Ursache der frühesten passenden Zeile zurück. Die Muster stehen schon in REGELN:
| Zeile enthält | Rückgabe |
|---|---|
Cannot find module oder ModuleNotFoundError | "abhaengigkeit" |
Konfiguration fehlt | "konfiguration" |
ECONNREFUSED oder ETIMEDOUT | "datenbank-netz" |
password authentication failed | "datenbank-login" |
didn't respond to HTTP pings on port | "port" |
Gross- und Kleinschreibung spielt keine Rolle. Passt keine Zeile, gib "unbekannt" zurück.
Dazu erwarteter_port(log): die Zahl nach on port: als int, oder None, wenn es keine solche Meldung gibt.
Warum die früheste Zeile? Stürzt die App beim Start ab, meldet die Plattform danach auch, dass niemand auf dem Port antwortet. Diese Meldung ist dann nur die Folge.
Typische Fehler
- Port fest im Code.
app.listen(3000)funktioniert lokal und scheitert in der Cloud mit 503. Korrektur:process.env.PORTlesen, 3000 nur als Rückfallwert. - Nur auf localhost hören. Ein Container, der auf
127.0.0.1hört, ist von aussen nicht erreichbar, auch nicht vom Proxy der Plattform. Korrektur: auf0.0.0.0hören (Express macht das ohne Host-Angabe automatisch). - Ohne Region anlegen. Wer
--locationweglässt, übernimmt eine Standardregion oder die der Ressourcengruppe und bemerkt es oft erst Monate später. Korrektur: Region in jedem Befehl explizit angeben und nach dem Anlegen prüfen. node_modulesmitschicken oder Build vergessen. Ein riesiges ZIP mit lokal kompilierten Paketen kann unter Linux scheitern, ein ZIP ohne Build-Einstellung startet ohne Pakete. Korrektur:node_modulesausschliessen undSCM_DO_BUILD_DURING_DEPLOYMENT=truesetzen.- Raten statt Logs lesen. Fünfmal neu deployen kostet Zeit. Korrektur: Log Stream öffnen und die erste Fehlermeldung suchen, spätere sind oft nur Folgen.
Zusammenfassung
- Bei PaaS lieferst du Code oder Container, die Plattform betreibt Server, Betriebssystem, Laufzeit, HTTPS und Neustarts.
- App Service passt für Code-Deployments klassischer Web-Apps, Container Apps für Docker-Images, Functions für kurze, ereignisgesteuerte Aufgaben.
- Personendaten bleiben in der Schweiz mit
switzerlandnorth(Azure) odereu-central-2(AWS). Die Region gibst du immer ausdrücklich an. - Ein Budget mit Alarm schützt dein Guthaben vor vergessenen Ressourcen.
- Deine App hört auf dem Port aus der Umgebungsvariable
PORT. Ein fester Port führt zu «didn't respond to HTTP pings» und Fehler 503. - Bei Problemen liest du zuerst den Log Stream (
az webapp log tail) und suchst die früheste Fehlermeldung.
Mit deiner eigenen KI vertiefen
Kopiere einen Prompt in Claude, ChatGPT oder Claude Code. Er macht die KI zur Lernbegleitung statt zum Lösungsautomaten.
PaaS und den App-Start sokratisch verstehen Chat-KI
Wenn dir noch nicht klar ist, was beim Start einer App auf App Service passiert und warum der Port so wichtig ist. Die KI fragt, bis du es selbst erklären kannst.