add synced notes on IT know how
This commit is contained in:
@@ -1 +0,0 @@
|
||||
/c/Users/d92448/OneDrive - d-fine GmbH/Notizbücher/mathias_dfine_vault/mathias_dfine_vault/Know-How/
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
## Modi
|
||||
```text
|
||||
i In den Insert-Modus (vor Cursor)
|
||||
I Insert am Zeilenanfang
|
||||
a Insert nach Cursor
|
||||
A Insert am Zeilenende
|
||||
o Neue Zeile unterhalb + Insert
|
||||
O Neue Zeile oberhalb + Insert
|
||||
|
||||
<Esc> Zurück in Normalmodus
|
||||
|
||||
v Visueller Modus (Zeichenweise)
|
||||
V Visueller Modus (Zeilenweise)
|
||||
<C-v> Visueller Blockmodus
|
||||
```
|
||||
## Navigation (Normalmodus)
|
||||
```text
|
||||
h j k l Links / runter / hoch / rechts
|
||||
|
||||
0 Zeilenanfang
|
||||
^ Erstes nicht-leer Zeichen
|
||||
$ Zeilenende
|
||||
|
||||
w Zum nächsten Wortanfang
|
||||
b Zum vorherigen Wortanfang
|
||||
e Zum Wortende
|
||||
W B E Wie oben, aber Worte durch Leerzeichen getrennt
|
||||
|
||||
gg Erste Zeile
|
||||
G Letzte Zeile
|
||||
ngg / nG Zu Zeile n
|
||||
|
||||
H Oberer Bildschirmrand
|
||||
M Mittlerer Bildschirmrand
|
||||
L Unterer Bildschirmrand
|
||||
|
||||
Ctrl-u Halbe Seite hoch
|
||||
Ctrl-d Halbe Seite runter
|
||||
Ctrl-b Seite hoch
|
||||
Ctrl-f Seite runter
|
||||
```
|
||||
## Basis-Bearbeitung
|
||||
```text
|
||||
x Zeichen unter Cursor löschen
|
||||
X Zeichen links vom Cursor löschen
|
||||
r<char> Zeichen ersetzen
|
||||
|
||||
dd Zeile löschen (cut)
|
||||
D Bis zum Zeilenende löschen
|
||||
cc Zeile ändern (delete + insert)
|
||||
C Bis zum Zeilenende ändern
|
||||
|
||||
yy Zeile kopieren
|
||||
Y Alias für yy
|
||||
p Einfügen NACH Cursor/Zeile
|
||||
P Einfügen VOR Cursor/Zeile
|
||||
|
||||
u Undo
|
||||
Ctrl-r Redo
|
||||
|
||||
J Nächste Zeile an aktuelle anhängen
|
||||
>> Zeile einrücken (indent)
|
||||
<< Zeile ausrücken (unindent)
|
||||
```
|
||||
## Mit Counts & Bewegungen
|
||||
Viele Befehle funktionieren mit einer Anzahl (count) und einer Bewegung:
|
||||
```text
|
||||
3w 3 Wörter vor
|
||||
5j 5 Zeilen runter
|
||||
|
||||
d3w 3 Wörter löschen
|
||||
c$ Bis Zeilenende ändern
|
||||
y0 Bis Zeilenanfang kopieren
|
||||
```
|
||||
## Textobjekte (im Normal- / Visuellen Modus)
|
||||
```text
|
||||
aw A word (inkl. Leerzeichen)
|
||||
iw Inner word
|
||||
as A sentence
|
||||
is Inner sentence
|
||||
ap A paragraph
|
||||
ip Inner paragraph
|
||||
|
||||
a" / i" Text in Anführungszeichen
|
||||
a' / i' Text in einfachen Quotes
|
||||
a) / i) Text in Klammern (usw. für {}, [], <>)
|
||||
|
||||
Beispiele:
|
||||
ci" Inhalt von "..." ändern
|
||||
da( Klammerausdruck inklusive Klammern löschen
|
||||
viw Wort unter Cursor markieren
|
||||
```
|
||||
## Suchen & Ersetzen
|
||||
```text
|
||||
/word Vorwärts nach „word“ suchen
|
||||
?word Rückwärts suchen
|
||||
n Nächster Treffer
|
||||
N Vorheriger Treffer
|
||||
|
||||
* Wort unter Cursor vorwärts suchen
|
||||
# Wort unter Cursor rückwärts suchen
|
||||
|
||||
:%s/alt/neu/g In gesamter Datei ersetzen
|
||||
:%s/alt/neu/gc Mit Bestätigung
|
||||
:.,$s/alt/neu/g Von aktueller Zeile bis Ende
|
||||
:10,20s/alt/neu/g In Zeilen 10–20
|
||||
```
|
||||
## Dateien, Buffers, Splits & Tabs
|
||||
```text
|
||||
:e datei Datei öffnen
|
||||
:w Speichern
|
||||
:w name Unter anderem Namen speichern
|
||||
:q Beenden
|
||||
:q! Beenden ohne Speichern
|
||||
:wq / :x Speichern und beenden
|
||||
:qa Alle Fenster schließen
|
||||
:qa! Alle schließen ohne Speichern
|
||||
|
||||
" Buffer (Dateien im Speicher)
|
||||
:ls Buffer-Liste
|
||||
:buffer n Buffer n öffnen
|
||||
:bnext / :bn Nächster Buffer
|
||||
:bprev / :bp Vorheriger Buffer
|
||||
:bdelete Buffer schließen
|
||||
|
||||
" Splits
|
||||
:split datei Horizontaler Split
|
||||
:vsplit datei Vertikaler Split
|
||||
Ctrl-w s Split horizontal
|
||||
Ctrl-w v Split vertikal
|
||||
Ctrl-w w Zum nächsten Split
|
||||
Ctrl-w h/j/k/l Split wechseln
|
||||
Ctrl-w q Split schließen
|
||||
|
||||
" Tabs
|
||||
:tabnew Neuer Tab
|
||||
:tabclose Tab schließen
|
||||
:tabnext / :tn Nächster Tab
|
||||
:tabprev / :tp Vorheriger Tab
|
||||
```
|
||||
## Visual Mode Aktionen
|
||||
```text
|
||||
v / V / Ctrl-v Auswahl starten
|
||||
y Auswahl kopieren
|
||||
d Auswahl löschen
|
||||
c Auswahl ändern
|
||||
|
||||
> Einrücken
|
||||
< Ausrücken
|
||||
= Auto-Indent (z.B. für Code)
|
||||
|
||||
: Befehl auf Auswahl anwenden
|
||||
(Bereich wird automatisch eingetragen)
|
||||
```
|
||||
## Makros & Wiederholung
|
||||
```text
|
||||
. Letzte Änderung wiederholen
|
||||
|
||||
q<a> Macro in Register a aufzeichnen
|
||||
(z.B. qa)
|
||||
... Aktionen ausführen
|
||||
q Aufzeichnung beenden
|
||||
|
||||
@a Macro a ausführen
|
||||
3@a Macro 3x ausführen
|
||||
@@ Letztes Macro wiederholen
|
||||
```
|
||||
## Register
|
||||
```text
|
||||
"0 Zuletzt kopiertes (y) ohne delete
|
||||
"1–"9 Verlaufsregister
|
||||
"" Default-Register
|
||||
|
||||
"+ System-Clipboard (kopieren/einfügen mit OS)
|
||||
"* Auswahl-Clipboard (unter Linux/X11)
|
||||
|
||||
Beispiele:
|
||||
"ayw Wort in Register a kopieren
|
||||
"ap Inhalt von Register a einfügen
|
||||
"+y In System-Clipboard kopieren
|
||||
"+p Aus System-Clipboard einfügen
|
||||
```
|
||||
## Kommandomodus (Ex-Befehle)
|
||||
```text
|
||||
:!cmd Externen Befehl ausführen
|
||||
:r !cmd Ausgabe von cmd einfügen
|
||||
:r datei Datei unter Cursor-Zeile einfügen
|
||||
|
||||
:set nu Zeilennummern an
|
||||
:set nonu Zeilennummern aus
|
||||
:set relativenumber Relative Nummern
|
||||
:set tabstop=4 shiftwidth=4 expandtab
|
||||
```
|
||||
## Einfache .vimrc-Beispiele
|
||||
```vim
|
||||
" Zeilennummern
|
||||
set number
|
||||
set relativenumber
|
||||
|
||||
" Einrückung
|
||||
set tabstop=4
|
||||
set shiftwidth=4
|
||||
set expandtab
|
||||
set smartindent
|
||||
|
||||
" Suche
|
||||
set ignorecase
|
||||
set smartcase
|
||||
set hlsearch
|
||||
set incsearch
|
||||
|
||||
" Maus
|
||||
set mouse=a
|
||||
```
|
||||
@@ -0,0 +1,497 @@
|
||||
#datenbank
|
||||
|
||||
➡️ [[#Zusammenfassung]]
|
||||
|
||||
---
|
||||
## 1. Grundidee: Was ist eine Identity Column?
|
||||
|
||||
Stell dir eine Tabelle „Kunden“ vor. Jeder Kunde soll eine eindeutige Nummer bekommen:
|
||||
|
||||
- Kunde 1
|
||||
- Kunde 2
|
||||
- Kunde 3
|
||||
- …
|
||||
|
||||
Du willst diese Nummern **nicht selbst vergeben**, sondern die Datenbank soll das **automatisch** machen, wenn du einen neuen Datensatz einfügst.
|
||||
|
||||
Genau das ist eine **Identity Column**:
|
||||
|
||||
> Eine Identity Column ist eine Spalte, deren Wert von der Datenbank automatisch erzeugt wird, meist als laufende Nummer (1, 2, 3, …). Sie wird oft als Primärschlüssel benutzt.
|
||||
|
||||
Typische Eigenschaften:
|
||||
|
||||
- numerisch (z. B. `INT`, `BIGINT`)
|
||||
- beim `INSERT` wird kein Wert angegeben – die DB füllt ihn selbst
|
||||
- der Wert ist pro Tabelle eindeutig
|
||||
- er steigt (meist) monoton an (1,2,3,…), kann aber Lücken haben
|
||||
|
||||
---
|
||||
|
||||
## 2. Ein einfaches Beispiel
|
||||
|
||||
Beispiel in **SQL Server**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY, -- Start bei 1, Inkrement 1
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Email NVARCHAR(200) NOT NULL
|
||||
);
|
||||
|
||||
-- Einfügen ohne KundeID anzugeben:
|
||||
INSERT INTO Kunde (Name, Email)
|
||||
VALUES ('Anna Beispiel', 'anna@example.com'),
|
||||
('Max Muster', 'max@muster.de');
|
||||
|
||||
-- Auslesen:
|
||||
SELECT * FROM Kunde;
|
||||
```
|
||||
|
||||
Ergebnis (vereinfacht):
|
||||
|
||||
```text
|
||||
KundeID | Name | Email
|
||||
--------+----------------+-------------------
|
||||
1 | Anna Beispiel | anna@example.com
|
||||
2 | Max Muster | max@muster.de
|
||||
```
|
||||
|
||||
Du hast `KundeID` nie selbst gesetzt – die Datenbank hat sie erzeugt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 Identity Column vs. Primärschlüssel
|
||||
|
||||
- **Primärschlüssel (Primary Key)**:
|
||||
Konzept: eine oder mehrere Spalten, die **eindeutig** einen Datensatz identifizieren.
|
||||
- **Identity Column**:
|
||||
Mechanismus: eine Spalte, deren Werte automatisch generiert werden.
|
||||
|
||||
Eine Identity Column **kann** der Primärschlüssel sein, muss aber nicht. Man kann auch:
|
||||
|
||||
- Identity Column + separater fachlicher Schlüssel (z. B. Kundennummer im ERP)
|
||||
- Primärschlüssel auf einer anderen Spalte (z. B. E-Mail) ohne Identity Column
|
||||
|
||||
In der Praxis: Sehr häufig ist die Identity Column = Primärschlüssel.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Identity Column vs. Auto-Increment
|
||||
|
||||
Viele Systeme benutzen unterschiedliche Begriffe:
|
||||
|
||||
- SQL Server: `IDENTITY`
|
||||
- PostgreSQL: `GENERATED AS IDENTITY` (früher `SERIAL`)
|
||||
- MySQL: `AUTO_INCREMENT`
|
||||
- Oracle: Identity-Spalten oder separate `SEQUENCE`
|
||||
|
||||
**Inhaltlich** ist „Identity Column“ oft gleichbedeutend mit „Auto-Increment-Spalte“: eine automatisch hochzählende Spalte.
|
||||
|
||||
PostgreSQL-Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
MySQL-Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(200) NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Identity Column vs. Sequence
|
||||
|
||||
**Sequence (Sequenz)** ist ein **eigenständiges Datenbankobjekt**, das Zahlen generiert, etwa so:
|
||||
|
||||
```sql
|
||||
CREATE SEQUENCE kunde_seq START WITH 1 INCREMENT BY 1;
|
||||
|
||||
SELECT nextval('kunde_seq'); -- gibt 1 zurück
|
||||
SELECT nextval('kunde_seq'); -- gibt 2 zurück
|
||||
```
|
||||
|
||||
Eine Identity Column ist oft intern an eine Sequence gekoppelt.
|
||||
Unterschied:
|
||||
|
||||
- Sequence: unabhängig von Tabellen, du kannst sie überall verwenden
|
||||
- Identity Column: an genau eine Spalte gebunden, automatischer Einsatz bei `INSERT`
|
||||
|
||||
In einigen DB-Systemen (z. B. Oracle, PostgreSQL) sind Identity Columns technisch eine bequeme Hülle um eine Sequence.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Identity Column vs. natürlicher/fachlicher Schlüssel
|
||||
|
||||
- **Natürlicher/fachlicher Schlüssel**: basiert auf echten Geschäfts-Daten
|
||||
Beispiele:
|
||||
- E-Mail-Adresse als eindeutiger Schlüssel für Benutzer
|
||||
- ISBN für Bücher
|
||||
- Personalnummer aus dem HR-System
|
||||
|
||||
- **Surrogate Key** (hier passt die Identity Column): technischer, künstlicher Schlüssel, ohne fachliche Bedeutung.
|
||||
|
||||
Identity Columns sind **Surrogate Keys**:
|
||||
- `KundeID = 42` sagt fachlich nichts über den Kunden aus
|
||||
- sie existiert nur, um Zeilen eindeutig zu identifizieren
|
||||
|
||||
In vielen Projekten arbeitet man mit:
|
||||
|
||||
- Identity Column als Primärschlüssel
|
||||
- dazu Unique-Constraints auf fachlichen Spalten (z. B. `Email UNIQUE`)
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Identity Column vs. UUID / GUID
|
||||
|
||||
Statt einer Identity Column (INT) kann man auch **UUIDs** (z. B. `UUID`, `uniqueidentifier`) als Primärschlüssel verwenden.
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- **Identity (INT/BIGINT)**:
|
||||
- kleiner, effizienter Index
|
||||
- gut lesbar (`KundeID = 123`)
|
||||
- nicht global eindeutig über mehrere Systeme, nur innerhalb der Tabelle
|
||||
- lässt leicht Rückschlüsse auf Anzahl/Abfolge (z. B. „wir sind bei Kunde 10.000“)
|
||||
|
||||
- **UUID**:
|
||||
- sehr große, „zufällig“ wirkende Zeichenfolge
|
||||
- global eindeutig (sehr hoch wahrscheinlich)
|
||||
- schwerer zu merken/lesen
|
||||
- Indexe sind meist größer, Einfügen kann teurer sein
|
||||
|
||||
Z. B. in PostgreSQL:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Identity Column und UUID sind also alternative Strategien zur ID-Erzeugung.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst eine Identity Column?
|
||||
|
||||
### 4.1 Eindeutige Identifikation von Datensätzen
|
||||
|
||||
Jede Zeile bekommt automatisch eine eindeutige ID:
|
||||
|
||||
- kein Risiko, dass zwei Datensätze „versehentlich“ dieselbe ID haben
|
||||
- Voraussetzung für saubere Joins, Fremdschlüssel, Referenzen
|
||||
|
||||
Beispiel: Fremdschlüssel von Auftrag auf Kunde:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE Auftrag (
|
||||
AuftragID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
KundeID INT NOT NULL,
|
||||
Betrag DECIMAL(10,2) NOT NULL,
|
||||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||||
);
|
||||
```
|
||||
|
||||
Beim Anlegen eines Auftrags verweist `Auftrag.KundeID` auf `Kunde.KundeID`.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Vereinfachung beim Einfügen von Daten
|
||||
|
||||
Ohne Identity Column müsstest du bei jedem `INSERT` einen neuen eindeutigen Wert berechnen und eintragen – in Mehrbenutzer-Umgebungen ist das fehleranfällig.
|
||||
|
||||
Mit Identity Column:
|
||||
|
||||
```sql
|
||||
INSERT INTO Kunde (Name) VALUES ('Lisa Test'); -- ID macht die DB
|
||||
```
|
||||
|
||||
Du musst dir keine Gedanken machen über:
|
||||
|
||||
- die nächste freie Nummer
|
||||
- Race Conditions (zwei Nutzer generieren zufällig dieselbe ID)
|
||||
- Sperren von Tabellen, etc.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Unterstützung von Mehrbenutzerbetrieb
|
||||
|
||||
In einer Datenbank arbeiten meist viele Nutzer oder Prozesse parallel. Identity Columns sind so implementiert, dass:
|
||||
|
||||
- gleichzeitig eingefügte Zeilen **ohne Kollision** IDs bekommen
|
||||
- du dich nicht um Synchronisation kümmern musst
|
||||
|
||||
Die Datenbank regelt intern:
|
||||
|
||||
- Sperren der Sequenz
|
||||
- Transaktionssicherheit
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Gute Performance und einfache Indizierung
|
||||
|
||||
Numerische Identity-Spalten:
|
||||
|
||||
- sind kompakt (z. B. 4 oder 8 Byte)
|
||||
- lassen sich effizient indexieren
|
||||
- wachsen meist monoton -> B-Tree-Indizes funktionieren sehr performant
|
||||
|
||||
Im Vergleich:
|
||||
- komplexe Primärschlüssel aus mehreren Textspalten sind größer, langsamer
|
||||
- Identity-Spalte als „technischer Schlüssel“ macht viele Operationen schneller
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen und typische Fallstricke
|
||||
|
||||
### 5.1 Lücken in der Nummernfolge
|
||||
|
||||
Anfänger erwarten oft: „Wenn ich einen Datensatz lösche oder eine Transaktion zurückrolle, füllt die DB die Nummer wieder auf.“
|
||||
|
||||
Das **passiert nicht** (und sollte auch nicht passieren).
|
||||
|
||||
Gründe für Lücken:
|
||||
|
||||
- Transaktion wird zurückgerollt nach dem Erzeugen einer ID
|
||||
- Datensatz mit ID 10 wird gelöscht
|
||||
- Parallel eingefügte Datensätze
|
||||
|
||||
Beispiel:
|
||||
- du fügst einen Datensatz ein -> bekommt ID 10
|
||||
- im gleichen Moment ein zweiter -> ID 11
|
||||
- deine Transaktion schlägt fehl -> Datensatz 10 existiert nicht
|
||||
- Datensatz 11 bleibt erhalten
|
||||
|
||||
Ergebnis: Es gibt jetzt mitunter keinen Datensatz mit ID 10. Das ist normal.
|
||||
|
||||
**Wichtige Praxisregel**:
|
||||
Eine Identity Column ist eine **technische ID**, keine „lückenlose Rechnungsnummer“, keine „fortlaufende Kundennummer“ im rechtlichen Sinn.
|
||||
|
||||
Wenn du wirklich lückenlose Nummern brauchst (z. B. für Rechnungen), wird das typischerweise anders gelöst (spezialisierte Logik, Sperren, eigene Tabellen).
|
||||
|
||||
---
|
||||
|
||||
### 5.2 „ID = Reihenfolge“ ist gefährlich
|
||||
|
||||
Menschen neigen dazu zu denken:
|
||||
|
||||
> „ID 100 wurde nach ID 99 erstellt.“
|
||||
|
||||
In der Praxis ist das oft, aber **nicht garantiert**:
|
||||
|
||||
- Backups/Restores
|
||||
- Replikation
|
||||
- Imports aus anderen Systemen
|
||||
- unterschiedliche Identity-Strategien
|
||||
|
||||
Besser: Wenn du die zeitliche Reihenfolge brauchst, verwende eine **Zeitstempel-Spalte**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100),
|
||||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||||
);
|
||||
```
|
||||
|
||||
Dann sortierst du nach `CreatedAt`, nicht nach `KundeID`.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Limits des Datentyps (Überlauf)
|
||||
|
||||
Wenn du `INT` (32-Bit) verwendest, ist irgendwann Schluss (ca. 2,1 Milliarden positive Werte). Bei sehr großen Tabellen oder sehr intensiver Nutzung kann das relevant werden.
|
||||
|
||||
Praktischer Tipp:
|
||||
|
||||
- lieber direkt `BIGINT` für Identity-Spalten verwenden – die Grenze ist so hoch, dass du in normalen Anwendungen nicht anstößt.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Bestellung (
|
||||
BestellungID BIGINT IDENTITY(1,1) PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Datenmigrationen, Importe, Reseeding
|
||||
|
||||
Problemfälle:
|
||||
|
||||
- du willst Daten von einem System ins andere migrieren
|
||||
- dort gibt es schon Datensätze mit Identity-Werten
|
||||
- beim Import dürfen keine IDs kollidieren
|
||||
|
||||
Strategien:
|
||||
|
||||
1. **Identity-Werte mit übernehmen**
|
||||
In vielen DB-Systemen kann man temporär eigene Werte setzen:
|
||||
|
||||
SQL Server:
|
||||
```sql
|
||||
SET IDENTITY_INSERT Kunde ON;
|
||||
|
||||
INSERT INTO Kunde (KundeID, Name, Email)
|
||||
VALUES (1001, 'Imported User', 'import@example.com');
|
||||
|
||||
SET IDENTITY_INSERT Kunde OFF;
|
||||
```
|
||||
|
||||
2. **Nach dem Import die Identity „neu starten“ (reseeden)**
|
||||
SQL Server:
|
||||
```sql
|
||||
DBCC CHECKIDENT ('Kunde', RESEED, 2000); -- nächste ID = 2001
|
||||
```
|
||||
|
||||
Diese Themen werden wichtig, wenn du Daten zwischen Systemen hin- und herschiebst.
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Verteilte Systeme / Sharding
|
||||
|
||||
In modernen Architekturen gibt es manchmal:
|
||||
|
||||
- mehrere Datenbankserver (Shards)
|
||||
- die später zusammengeführt werden sollen
|
||||
|
||||
Wenn jede Tabelle eine Identity-Spalte bei 1 beginnen lässt, kann es bei Merge-Vorgängen Kollisionen geben (z. B. `KundeID = 100` existiert auf zwei Servern mit unterschiedlichen Kunden).
|
||||
|
||||
Lösungen:
|
||||
|
||||
- pro Server unterschiedliche Startwerte und Inkremente:
|
||||
- Server A: `IDENTITY(1, 3)` → 1,4,7,…
|
||||
- Server B: `IDENTITY(2, 3)` → 2,5,8,…
|
||||
- Server C: `IDENTITY(3, 3)` → 3,6,9,…
|
||||
- oder Nutzung von UUIDs statt Identity-Spalten
|
||||
|
||||
---
|
||||
|
||||
### 5.6 Informationsleck (Datenschutz, Sicherheit)
|
||||
|
||||
Außen sichtbare IDs (z. B. in URLs) können Informationen verraten:
|
||||
|
||||
- wenn dein Kunde eine URL wie `/bestellung/1000` sieht, kann er ahnen, dass es ~1000 Bestellungen gibt
|
||||
- er kann versuchen, `/bestellung/999` aufzurufen (ID-Raten)
|
||||
|
||||
Das ist kein Problem der Identity-Spalte an sich, sondern der Entscheidung, **diese ID außerhalb der Anwendung sichtbar** zu machen.
|
||||
|
||||
Lösungen:
|
||||
|
||||
- Zugriffskontrollen („zeige nur eigene Bestellungen“)
|
||||
- andere, „nicht-erratbare“ IDs für externe Darstellung (z. B. UUIDs, Hashes)
|
||||
|
||||
---
|
||||
|
||||
### 5.7 Portabilität zwischen Datenbanksystemen
|
||||
|
||||
Jede Datenbank hat leicht andere Syntax:
|
||||
|
||||
- SQL Server: `INT IDENTITY(1,1)`
|
||||
- PostgreSQL: `GENERATED ALWAYS AS IDENTITY`
|
||||
- MySQL: `AUTO_INCREMENT`
|
||||
- Oracle: `GENERATED BY DEFAULT AS IDENTITY` oder Sequences
|
||||
|
||||
Wenn du Wert auf **Portabilität** legst (dass dein Schema in mehreren DB-Systemen läuft), musst du darauf achten:
|
||||
|
||||
- eher Standard-SQL (`GENERATED [ALWAYS|BY DEFAULT] AS IDENTITY`)
|
||||
- oder ID-Generierung in der Anwendung selbst
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Gesamtbeispiele
|
||||
|
||||
### 6.1 Einfache Kunden- und Bestellverwaltung (SQL Server-Variante)
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Email NVARCHAR(200) NOT NULL UNIQUE,
|
||||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||||
);
|
||||
|
||||
CREATE TABLE Bestellung (
|
||||
BestellungID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
KundeID INT NOT NULL,
|
||||
Datum DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
|
||||
Betrag DECIMAL(10, 2) NOT NULL,
|
||||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||||
);
|
||||
|
||||
-- Neuen Kunden anlegen:
|
||||
INSERT INTO Kunde (Name, Email)
|
||||
VALUES ('Anna Beispiel', 'anna@example.com');
|
||||
|
||||
-- Die ID des eben eingefügten Kunden holen:
|
||||
SELECT SCOPE_IDENTITY() AS NeueKundeID;
|
||||
```
|
||||
|
||||
`SCOPE_IDENTITY()` gibt dir in SQL Server die zuletzt erzeugte Identity in der aktuellen Session und dem aktuellen Scope. Damit kannst du *direkt danach* eine Bestellung für genau diesen Kunden anlegen.
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Beispiel in PostgreSQL (moderner Standard)
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE bestellung (
|
||||
bestellung_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
kunde_id BIGINT NOT NULL REFERENCES kunde(kunde_id),
|
||||
datum TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
betrag NUMERIC(10,2) NOT NULL
|
||||
);
|
||||
|
||||
-- Einfügen:
|
||||
INSERT INTO kunde (name, email)
|
||||
VALUES ('Max Muster', 'max@muster.de')
|
||||
RETURNING kunde_id; -- PostgreSQL-spezifisch, um die ID direkt zu bekommen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- Eine **Identity Column** ist eine Spalte, deren Wert die Datenbank automatisch erzeugt (typisch: laufende Nummer).
|
||||
- Sie wird häufig als **Primärschlüssel** verwendet, ist aber **nicht dasselbe** wie der Begriff „Primärschlüssel“.
|
||||
- Sie löst v. a. diese Probleme:
|
||||
- eindeutige Identifikation von Zeilen
|
||||
- einfache Inserts, ohne selbst IDs zu erzeugen
|
||||
- sichere Nutzung in Mehrbenutzer-Umgebungen
|
||||
- gute Performance bei Indexen und Joins
|
||||
- Sie ist verwandt mit:
|
||||
- Auto-Increment-Spalten (`AUTO_INCREMENT`, `SERIAL`)
|
||||
- Sequences (`SEQUENCE`)
|
||||
- Surrogate Keys im Gegensatz zu natürlichen Schlüsseln
|
||||
- Alternativen wie UUID/GUID
|
||||
- Typische Herausforderungen:
|
||||
- Lücken in der Nummernfolge (normal und erwünscht)
|
||||
- ID ist nicht zuverlässig die zeitliche Reihenfolge
|
||||
- Datenmigration, Reseeding, Sharding
|
||||
- potenzielles Informationsleck, wenn IDs öffentlich sichtbar sind
|
||||
- Unterschiede in der Implementierung zwischen Datenbanksystemen
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
## 1. Grundidee: Was ist ein ORM?
|
||||
|
||||
ORM steht für **Object-Relational Mapping**.
|
||||
Im Kontext von Python und Datenbankmodellen bedeutet das:
|
||||
|
||||
> Ein ORM ist eine Bibliothek, die es dir erlaubt, mit einer relationalen Datenbank (z. B. PostgreSQL, MySQL, SQLite) zu arbeiten, indem du **Python-Klassen und -Objekte** verwendest statt SQL-Strings.
|
||||
|
||||
Anstatt also SQL wie:
|
||||
|
||||
```sql
|
||||
SELECT * FROM konto WHERE id = 1;
|
||||
```
|
||||
|
||||
zu schreiben, machst du in Python z. B.:
|
||||
|
||||
```python
|
||||
konto = session.get(Konto, 1)
|
||||
```
|
||||
|
||||
Das ORM übersetzt deine Python-Anweisung im Hintergrund in SQL, führt sie aus und gibt dir Python-Objekte zurück.
|
||||
|
||||
---
|
||||
|
||||
## 2. Wichtige Begriffe und Bausteine (intuitiv erklärt)
|
||||
|
||||
Nehmen wir ein vereinfachtes Bank-Beispiel:
|
||||
|
||||
- **Kunde** (Customer)
|
||||
- **Konto** (Account)
|
||||
- **Transaktion** (Transaction)
|
||||
|
||||
In einer relationalen Datenbank wären das Tabellen:
|
||||
|
||||
- `kunde`
|
||||
- `konto`
|
||||
- `transaktion`
|
||||
|
||||
Mit einem ORM legst du dafür **Modelle** als Python-Klassen an:
|
||||
|
||||
```python
|
||||
class Kunde:
|
||||
...
|
||||
class Konto:
|
||||
...
|
||||
class Transaktion:
|
||||
...
|
||||
```
|
||||
|
||||
Die wichtigsten Bausteine:
|
||||
|
||||
- **Modell / Entity**:
|
||||
Eine Klasse, die eine Tabelle repräsentiert (z. B. `Konto` → Tabelle `konto`).
|
||||
- **Felder / Spalten**:
|
||||
Attribute der Klasse (z. B. `saldo`, `kunde_id`).
|
||||
- **Beziehungen**:
|
||||
- „Ein Kunde hat viele Konten“ (One-to-Many)
|
||||
- „Ein Konto gehört zu genau einem Kunden“ (Many-to-One)
|
||||
- **Session / EntityManager**:
|
||||
Ein Objekt, über das du mit der Datenbank sprichst (z. B. Datensätze abfragen, speichern, löschen).
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 ORM vs. direkte SQL-Nutzung
|
||||
|
||||
**Direkte SQL-Nutzung:**
|
||||
|
||||
- Du schreibst selbst SQL-Statements.
|
||||
- Du verwendest z. B. `psycopg2` (PostgreSQL) oder `sqlite3` (Standard in Python).
|
||||
- Du bekommst Resultate als Tupel / Dictionaries zurück und baust selbst deine Objekte.
|
||||
|
||||
**Mit ORM:**
|
||||
|
||||
- Du arbeitest mit Python-Klassen und -Objekten.
|
||||
- Das ORM generiert und führt SQL für dich aus.
|
||||
- Du bekommst direkt Instanzen deiner Klassen.
|
||||
|
||||
```python
|
||||
# Direkte SQL-Nutzung
|
||||
cursor.execute("SELECT id, name FROM kunde WHERE id = %s", (1,))
|
||||
row = cursor.fetchone()
|
||||
kunde = {"id": row[0], "name": row[1]}
|
||||
|
||||
# ORM
|
||||
kunde = session.get(Kunde, 1)
|
||||
print(kunde.name)
|
||||
```
|
||||
|
||||
ORM nimmt dir also den „manuellen“ Teil des Mappings ab.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 ORM vs. Datenbankmodellierung (ER-Modell)
|
||||
|
||||
- **Datenbankmodellierung** (ER-Diagramme, Normalisierung usw.) ist der Schritt, in dem du **konzipierst**, wie deine Daten strukturiert sind.
|
||||
- **ORM** ist ein **Werkzeug**, um mit dieser Struktur in Python zu arbeiten.
|
||||
|
||||
Du kannst ein gutes Datenbankmodell haben – **mit oder ohne** ORM.
|
||||
ORM ersetzt nicht das Nachdenken über ein sinnvolles Datenmodell.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 ORM vs. Migrations-Tools
|
||||
|
||||
- **Migrations-Tools** (z. B. Alembic für SQLAlchemy, Django-Migrations) verwalten die **Versionierung und Änderungen** am Schema (z. B. neue Spalte, geänderte Spalte).
|
||||
- Ein **ORM** arbeitet primär zur **Laufzeit** mit Daten; viele ORMs bringen allerdings Tools mit, um aus den Modellen Migrationen zu erzeugen.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 ORM vs. Query-Builder
|
||||
|
||||
- **Query-Builder**: Bibliothek, die das Schreiben von SQL erleichtert, aber nicht unbedingt Objekte modelliert (z. B. SQLAlchemy Core).
|
||||
- **ORM**: baut auf einem Query-Builder auf und bringt zusätzlich:
|
||||
- Klassen→Tabellen-Mapping
|
||||
- Objekte→Zeilen-Mapping
|
||||
- Beziehungen als Attribute
|
||||
|
||||
---
|
||||
|
||||
### 3.5 ORM vs. [[Pydantic]] / Dataclasses
|
||||
|
||||
- **[[Pydantic]] / `dataclasses`**: Modellieren **In-Memory-Daten** (z. B. Input aus einer API), inkl. Validation und Typen.
|
||||
- **ORM**: Modelliert **persistente Daten** in einer relationalen Datenbank.
|
||||
|
||||
Man kombiniert das oft:
|
||||
|
||||
- [[Pydantic]]-Modelle für API-Ein-/Ausgaben
|
||||
- ORM-Modelle für Speicherung in der Datenbank
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst ein ORM?
|
||||
|
||||
### 4.1 „Impedance Mismatch“: Objekte vs. Tabellen
|
||||
|
||||
Python arbeitet mit **Objekten**:
|
||||
|
||||
```python
|
||||
kunde.name
|
||||
konto.saldo
|
||||
konto.kunde.name
|
||||
```
|
||||
|
||||
Datenbanken arbeiten mit **Tabellen**, **Zeilen** und **Fremdschlüsseln**.
|
||||
ORMs „übersetzen“ zwischen diesen Welten.
|
||||
|
||||
Beispiel:
|
||||
|
||||
- `konto.kunde` ist in Python einfach ein Attribut.
|
||||
- Intern bedeutet das: Joins über `konto.kunde_id = kunde.id`.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Weniger Boilerplate, mehr Fokus auf Fachlogik
|
||||
|
||||
Ohne ORM schreibst du viel repetiven Code:
|
||||
|
||||
- SQL-Strings
|
||||
- Parameter-Bindung
|
||||
- Zeilen in Python-Objekte umwandeln
|
||||
|
||||
Mit ORM schreibst du einmal deine Modelle und konzentrierst dich dann auf:
|
||||
|
||||
- „Neue Transaktion buchen“
|
||||
- „Saldo prüfen“
|
||||
- „Kontoauszug generieren“
|
||||
|
||||
Anstatt: „Wie formuliere ich nochmal den SQL-Join...“.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Typisierung, Autocomplete, Konsistenz
|
||||
|
||||
Durch Python-Klassen hast du:
|
||||
|
||||
- **Typhinweise** (z. B. `saldo: float`)
|
||||
- Unterstützung durch IDE (Autocomplete, Refactoring)
|
||||
- Klar definierte Beziehungen (z. B. `konto.kunde`)
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Datenbank-Agnostik
|
||||
|
||||
Viele ORMs unterstützen mehrere Datenbanken:
|
||||
|
||||
- Du kannst z. B. in Tests SQLite verwenden,
|
||||
- in Produktion PostgreSQL,
|
||||
- ohne deinen ganzen Code umzuschreiben (meist nur Konfiguration).
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Testbarkeit
|
||||
|
||||
Du kannst:
|
||||
|
||||
- einfacher Unit-Tests schreiben, indem du In-Memory-SQLite nutzt,
|
||||
- oder sogar nur mit „Fake-Repositories“ arbeitest, die sich wie das ORM verhalten.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Herausforderungen bringen ORMs mit sich?
|
||||
|
||||
### 5.1 Performance-Fallen („N+1-Problem“, zu viele Queries)
|
||||
|
||||
Beispiel: Du lädst 100 Konten und für jedes Konto den zugehörigen Kunden:
|
||||
|
||||
Naiv:
|
||||
|
||||
```python
|
||||
konten = session.query(Konto).all()
|
||||
for konto in konten:
|
||||
print(konto.kunde.name)
|
||||
```
|
||||
|
||||
Kann bedeuten:
|
||||
|
||||
- 1 Query für alle Konten
|
||||
- + 100 Queries für jeden einzelnen Kunden
|
||||
|
||||
→ Insgesamt 101 Queries (= N+1-Problem).
|
||||
|
||||
Mit ORM musst du lernen, wie man:
|
||||
|
||||
- **Eager Loading** / `join` / `selectinload` etc. nutzt,
|
||||
- um nur **1–2 Queries** zu erzeugen.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Man darf SQL nicht völlig „vergessen“
|
||||
|
||||
ORM nimmt viel Arbeit ab, aber:
|
||||
|
||||
- du solltest **verstehen**, was für SQL generiert wird,
|
||||
- und Grundbegriffe wie `JOIN`, `WHERE`, `GROUP BY`, Indexe kennen.
|
||||
|
||||
Ohne grundlegende SQL-Kenntnisse tappst du schnell in Performance-Probleme.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Komplexe Abfragen
|
||||
|
||||
Für einfache Abfragen ist ORM sehr angenehm.
|
||||
Bei sehr komplexen Auswertungen (z. B. Bank-Reporting mit vielen Aggregationen, Window-Funktionen) kann:
|
||||
|
||||
- die ORM-Syntax unübersichtlich werden,
|
||||
- rohe SQL-Statements manchmal klarer und effizienter sein.
|
||||
|
||||
Viele ORMs erlauben gemischt:
|
||||
|
||||
- 90 % ORM,
|
||||
- 10 % direktes SQL für Spezialfälle.
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Migrationen und Schema-Änderungen
|
||||
|
||||
- Du musst im Blick behalten: **Modelle in Python** und **Schema in Datenbank** dürfen nicht auseinanderlaufen.
|
||||
- Migrations-Tools sind notwendig, aber auch ein eigener Lernbereich.
|
||||
|
||||
---
|
||||
|
||||
### 5.5 „Lock-In“ und Komplexität
|
||||
|
||||
- Große ORMs (z. B. SQLAlchemy ORM, Django ORM) sind mächtig, aber haben Lernkurve.
|
||||
- Wenn du einmal dein ganzes Projekt auf ein bestimmtes ORM gebaut hast, ist ein Wechsel auf ein anderes ORM oder „plain SQL“ aufwendig.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele mit Python (SQLAlchemy ORM)
|
||||
|
||||
### 6.1 Setup: einfache Bank-Domain mit SQLAlchemy
|
||||
|
||||
Installation:
|
||||
|
||||
```bash
|
||||
pip install sqlalchemy
|
||||
```
|
||||
|
||||
Ein minimaler Aufbau mit `Kunde` und `Konto`:
|
||||
|
||||
```python
|
||||
from sqlalchemy import (
|
||||
create_engine, Column, Integer, String, Float, ForeignKey
|
||||
)
|
||||
from sqlalchemy.orm import (
|
||||
declarative_base, relationship, Session
|
||||
)
|
||||
|
||||
# Basis-Klasse für alle ORM-Modelle
|
||||
Base = declarative_base()
|
||||
|
||||
class Kunde(Base):
|
||||
__tablename__ = "kunde"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, nullable=False)
|
||||
|
||||
# Beziehung: Ein Kunde hat viele Konten
|
||||
konten = relationship("Konto", back_populates="kunde")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Kunde id={self.id} name={self.name!r}>"
|
||||
|
||||
class Konto(Base):
|
||||
__tablename__ = "konto"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
kontonummer = Column(String, unique=True, nullable=False)
|
||||
saldo = Column(Float, default=0.0)
|
||||
|
||||
kunde_id = Column(Integer, ForeignKey("kunde.id"), nullable=False)
|
||||
# Beziehung: Konto gehört zu genau einem Kunden
|
||||
kunde = relationship("Kunde", back_populates="konten")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Konto id={self.id} konto={self.kontonummer!r} saldo={self.saldo}>"
|
||||
|
||||
# Engine und Session einrichten (hier: SQLite-Datei)
|
||||
engine = create_engine("sqlite:///bank.db", echo=True) # echo=True zeigt SQL an
|
||||
Base.metadata.create_all(engine) # Tabellen aus den Modellen erzeugen
|
||||
|
||||
# Session erstellen
|
||||
session = Session(engine)
|
||||
|
||||
# Beispiel-Daten anlegen
|
||||
kunde = Kunde(name="Max Mustermann")
|
||||
konto1 = Konto(kontonummer="DE123", saldo=1000.0, kunde=kunde)
|
||||
konto2 = Konto(kontonummer="DE456", saldo=2500.0, kunde=kunde)
|
||||
|
||||
session.add(kunde) # reicht, da konten via Beziehung mit hinzugefügt werden
|
||||
session.commit()
|
||||
|
||||
# Abfragen
|
||||
alle_kunden = session.query(Kunde).all()
|
||||
print(alle_kunden)
|
||||
|
||||
# Zugriff auf Beziehungen
|
||||
for k in alle_kunden:
|
||||
print(f"Kunde: {k.name}")
|
||||
for konto in k.konten:
|
||||
print(f" Konto {konto.kontonummer}, Saldo: {konto.saldo}")
|
||||
|
||||
# Einzelnes Konto laden und zugehörigen Kunden ausgeben
|
||||
konto = session.query(Konto).filter_by(kontonummer="DE123").one()
|
||||
print(konto.kunde.name)
|
||||
|
||||
session.close()
|
||||
```
|
||||
|
||||
Wichtige Punkte im Beispiel:
|
||||
|
||||
- `Kunde` und `Konto` sind Python-Klassen, die Tabellen repräsentieren.
|
||||
- Die Beziehung `Kunde.konten` und `Konto.kunde` erlaubt dir objektorientierten Zugriff.
|
||||
- Das ORM generiert automatisch SQL (sichtbar durch `echo=True`).
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Gleiche Logik mit „plain SQL“ (zum Vergleich)
|
||||
|
||||
Zum Vergleich ein stark vereinfachendes Beispiel mit `sqlite3`:
|
||||
|
||||
```python
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect("bank_plain.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Tabellen anlegen
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS kunde (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS konto (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kontonummer TEXT NOT NULL UNIQUE,
|
||||
saldo REAL DEFAULT 0.0,
|
||||
kunde_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(kunde_id) REFERENCES kunde(id)
|
||||
)
|
||||
""")
|
||||
|
||||
# Kunde anlegen
|
||||
cursor.execute("INSERT INTO kunde (name) VALUES (?)", ("Max Mustermann",))
|
||||
kunde_id = cursor.lastrowid
|
||||
|
||||
# Konten anlegen
|
||||
cursor.execute(
|
||||
"INSERT INTO konto (kontonummer, saldo, kunde_id) VALUES (?, ?, ?)",
|
||||
("DE123", 1000.0, kunde_id)
|
||||
)
|
||||
cursor.execute(
|
||||
"INSERT INTO konto (kontonummer, saldo, kunde_id) VALUES (?, ?, ?)",
|
||||
("DE456", 2500.0, kunde_id)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# Kunden mit Konten abfragen
|
||||
cursor.execute("""
|
||||
SELECT k.name, ko.kontonummer, ko.saldo
|
||||
FROM kunde k
|
||||
JOIN konto ko ON ko.kunde_id = k.id
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
for row in rows:
|
||||
name, kontonummer, saldo = row
|
||||
print(name, kontonummer, saldo)
|
||||
|
||||
conn.close()
|
||||
```
|
||||
|
||||
Hier musst du:
|
||||
|
||||
- SQL selbst schreiben,
|
||||
- `rows` manuell in Python-Strukturen übersetzen.
|
||||
|
||||
Mit ORM übernimmt der ORM-Layer:
|
||||
|
||||
- das Mapping,
|
||||
- das Auflösen der Beziehungen,
|
||||
- einiges an Boilerplate.
|
||||
|
||||
---
|
||||
|
||||
## 7. Speziell im Bank-/Finanz-Kontext
|
||||
|
||||
Gerade im Bankumfeld ist ORM attraktiv, weil:
|
||||
|
||||
- es viele **fachliche Entitäten** gibt (Konto, Kunde, Vertrag, Produkt, Transaktion, Wertpapier, Order, …),
|
||||
- du komplexe **Geschäftslogik** in Python implementieren willst,
|
||||
- und dabei konsistent auf persistente Daten zugreifen musst.
|
||||
|
||||
Typische Muster:
|
||||
|
||||
- **Transaktions-Logs** (jede Buchung als eigener Datensatz),
|
||||
- **Audit-Trails** (wer hat wann was geändert),
|
||||
- **Referenzdaten** (Währungen, Länder, Produktstammdaten).
|
||||
|
||||
ORMs helfen:
|
||||
|
||||
- diese Entitäten sauber als Klassen zu modellieren,
|
||||
- Beziehungen explizit zu machen,
|
||||
- und fachliche Operationen wie „Buchung durchführen“ objektorientiert abzubilden.
|
||||
|
||||
---
|
||||
|
||||
## 8. Wann lohnt sich ein ORM – und wann eher nicht?
|
||||
|
||||
**Sinnvoll:**
|
||||
|
||||
- Du baust eine mittlere bis große Anwendung mit vielen Entitäten.
|
||||
- Du willst langfristig Wartbarkeit, Tests und Refactoring erleichtern.
|
||||
- Du hast Standard-CRUD-Operationen (create/read/update/delete) und „normale“ fachliche Logik.
|
||||
|
||||
**Eher nicht sinnvoll:**
|
||||
|
||||
- Kleine Skripte, die nur ein paar einfache SQL-Abfragen ausführen.
|
||||
- Hochoptimierte Reporting- oder Analytics-Abfragen, die ohnehin spezielle SQL-Funktionen nutzen.
|
||||
- Wenn dein Team sehr SQL-affin ist und kein Interesse an der zusätzlichen Abstraktionsschicht hat.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zusammenfassung
|
||||
|
||||
- **ORM** ist eine Technik (und meist eine Bibliothek), um **relationalen Datenbanken** über **Objekte** und **Klassen** zu begegnen.
|
||||
- Sie löst das Mapping zwischen Tabellen/Zeilen und Klassen/Objekten und reduziert manuellen SQL-Boilerplate.
|
||||
- Sie bietet Vorteile in Wartbarkeit, Typisierung, Testbarkeit – gerade bei komplexeren Domänen wie Banking.
|
||||
- Gleichzeitig bringt ein ORM neue Herausforderungen mit sich: Performance-Fallen, Lernaufwand, Verständnis der generierten SQL-Abfragen bleibt wichtig.
|
||||
- In Python sind gängige ORMs: **[[SQLAlchemy]] ORM**, **Django ORM**, (in moderneren Stacks oft in Kombination mit [[Pydantic]], [[FastAPI]] etc.).
|
||||
|
||||
Executable
+346
@@ -0,0 +1,346 @@
|
||||
Hier eine kompakte, praxisorientierte Übersicht zu PostgreSQL mit Fokus auf „Was ist anders als in Oracle?“ und „Was sollte ich vor dem Umstieg lernen?“.
|
||||
|
||||
---
|
||||
|
||||
## 1. Kurzüberblick & Gemeinsamkeiten
|
||||
|
||||
Gemeinsamkeiten (nur kurz, da dir das meiste vertraut ist):
|
||||
|
||||
- Relationales DBMS, SQL-basiert, ANSI-konform
|
||||
- [[ACID-Transaktionen]], [[MVCC]], Isolation Levels
|
||||
- Sequences, Views, [[Materialized Views]], Trigger, Stored Procedures/Functions
|
||||
- Joins, Subselects, Window Functions, [[CTEs]] (`WITH`), etc.
|
||||
- Rolle-/Rechtemodell, Schemas
|
||||
|
||||
---
|
||||
|
||||
## 2. Zentrale Unterschiede auf einen Blick
|
||||
|
||||
Die wichtigsten Lernfelder im Übergang von Oracle zu PostgreSQL:
|
||||
|
||||
1. **Namensräume & `search_path` statt Synonyme**
|
||||
2. **Datentypen & Funktionalität (z.B. `NUMERIC`, `SERIAL`, `JSONB`, Arrays)**
|
||||
3. **Sequences & Identity-Spalten**
|
||||
4. **PL/pgSQL vs. PL/SQL (kein Paketkonzept)**
|
||||
5. **MVCC-Implementierung & VACUUM/Autovacuum**
|
||||
6. **DDL in Transaktionen, Auto-Commit-Verhalten**
|
||||
7. **Index-Typen & Besonderheiten (GIN/GiST, Partial-, Expression-Indexe)**
|
||||
8. **Partitionierung (deutlich anders als ältere Oracle-Partitioning-Konzepte)**
|
||||
9. **Admin & Tools (psql, Konfigurationsparameter, Monitoring)**
|
||||
|
||||
---
|
||||
|
||||
## 3. Schemata, Namensauflösung & Synonyme
|
||||
|
||||
### Schemata & `search_path`
|
||||
|
||||
In PostgreSQL sind Schemata ähnlich wie in Oracle. Es gibt zusätzlich einen **`search_path`**, der festlegt, in welcher Reihenfolge Schemata nach Objekten durchsucht werden.
|
||||
|
||||
```sql
|
||||
SHOW search_path;
|
||||
SET search_path TO app, public;
|
||||
```
|
||||
|
||||
Statt Synonymen (Oracle) wird oft mit `search_path` gearbeitet:
|
||||
|
||||
- Kein `CREATE SYNONYM` in PostgreSQL.
|
||||
- Alternative: Views in einem „zentrale“ Schema, `search_path` setzen, oder `CREATE VIEW` als Abstraktionsschicht.
|
||||
|
||||
**Lernpunkt:** Überleg dir, wie du Synonyme ablöst – meistens durch eine Kombination aus `search_path`, Views und ggf. konsistenter Schema-Namenskonvention.
|
||||
|
||||
---
|
||||
|
||||
## 4. Datentypen & SQL-Dialekt
|
||||
|
||||
### Wichtige Unterschiede bei Datentypen
|
||||
|
||||
- `NUMBER` → in der Regel `NUMERIC(p,s)` oder `INTEGER`, `BIGINT`.
|
||||
- `VARCHAR2` → `VARCHAR(n)` oder `TEXT`. PostgreSQL hat keinen Unterschied zwischen `VARCHAR` und `VARCHAR2`.
|
||||
- `DATE` in PostgreSQL enthält Datum **und Uhrzeit** (wie Oracle `DATE`). Für „reine“ Datumswerte: `date`, für Zeitpunkte mit Zeitzone: `timestamptz`.
|
||||
- `CLOB`/`BLOB` → meist `TEXT` bzw. `BYTEA`.
|
||||
- Zusätzliche Typen:
|
||||
- `JSON`/`JSONB`
|
||||
- Arrays (`integer[]`, `text[]` etc.)
|
||||
- Geodaten (via Extension PostGIS)
|
||||
- `UUID`, `Inet`, `CIDR` etc.
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(200),
|
||||
email TEXT,
|
||||
erstellt_am timestamptz DEFAULT now(),
|
||||
daten JSONB
|
||||
);
|
||||
```
|
||||
|
||||
### Funktionen und Syntax-Details
|
||||
|
||||
- String-Konkatenation: `||` (wie Oracle).
|
||||
- `NVL` → `COALESCE` (Standard); es gibt auch `NULLIF`.
|
||||
- `DECODE` → `CASE WHEN ... THEN ... ELSE ... END`.
|
||||
- `ROWNUM` → `LIMIT` / `OFFSET` oder Window Functions (`row_number()`).
|
||||
- Kein „`FROM dual`“ nötig, du kannst einfach:
|
||||
```sql
|
||||
SELECT 1;
|
||||
```
|
||||
|
||||
**Lernpunkt:** Zuordnen der wichtigsten Oracle-Funktionen zu PostgreSQL-Äquivalenten (`DECODE` → `CASE`, `NVL` → `COALESCE`, `ROWNUM` → `LIMIT`/Window).
|
||||
|
||||
---
|
||||
|
||||
## 5. Sequences & Identity-Spalten
|
||||
|
||||
In Oracle: `CREATE SEQUENCE ...` + Trigger oder `IDENTITY` in neueren Versionen.
|
||||
|
||||
In PostgreSQL gibt es mehrere Varianten:
|
||||
|
||||
- Klassische Sequence:
|
||||
```sql
|
||||
CREATE SEQUENCE myseq;
|
||||
SELECT nextval('myseq');
|
||||
```
|
||||
|
||||
- „Legacy“ Auto-Increment:
|
||||
```sql
|
||||
CREATE TABLE t (
|
||||
id SERIAL PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
- Standardkonforme [[Identity Columns]] (empfohlen):
|
||||
```sql
|
||||
CREATE TABLE t (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
**Lernpunkt:** Umstieg auf `GENERATED AS IDENTITY` planen, `SERIAL` verstehen (ist syntaktischer Zucker für Sequence + Default).
|
||||
|
||||
---
|
||||
|
||||
## 6. PL/pgSQL vs. PL/SQL (Packages, Prozeduren, Funktionen)
|
||||
|
||||
### Kein Paketkonzept
|
||||
|
||||
PostgreSQL hat keine Packages wie Oracle (`package spec/body`). Stattdessen:
|
||||
|
||||
- Funktionen stehen „flach“ in einem Schema.
|
||||
- Ähnliches Strukturieren über Namenskonventionen, Schemata, Extensions.
|
||||
|
||||
### PL/pgSQL
|
||||
|
||||
Syntax ähnlich PL/SQL, aber mit einigen Unterschieden:
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION addiere(a int, b int)
|
||||
RETURNS int
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
res int;
|
||||
BEGIN
|
||||
res := a + b;
|
||||
RETURN res;
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
Ab PostgreSQL 11 gibt es auch **Stored Procedures** (ohne Rückgabewert, aufrufbar mit `CALL`) – im Unterschied zu Funktionen, die in SQL-Ausdrücke eingebettet werden können.
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Unterschiede in Ausnahmebehandlung und Cursor-Syntax im Detail.
|
||||
- Kein Paket-Overloading wie in Oracle – Overloading geht, aber ohne Packages.
|
||||
- Migration von Package-Variablen: oft durch Tabellen, Konfigurationstabellen oder `SET LOCAL` + GUC-Parameter ersetzen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Transaktionen, MVCC & Locks
|
||||
|
||||
### MVCC-Implementierung
|
||||
|
||||
Beide nutzen MVCC, aber die Implementierung ist anders:
|
||||
|
||||
- PostgreSQL speichert mehrere Versionen von Zeilen (Tuples) im Heap.
|
||||
- Gelöschte/veraltete Versionen werden nicht sofort entfernt, sondern durch **VACUUM** aufgeräumt.
|
||||
- Standard: Autovacuum kümmert sich darum. Manchmal Tuning nötig (`autovacuum_*`-Parameter).
|
||||
|
||||
### Isolation Levels
|
||||
|
||||
Standard-Level in PostgreSQL ist `READ COMMITTED`, `REPEATABLE READ` ist nicht exakt wie Oracle `SERIALIZABLE`. Es gibt zusätzlich ein echtes `SERIALIZABLE` via SSI (Serializable Snapshot Isolation).
|
||||
|
||||
```sql
|
||||
SHOW default_transaction_isolation;
|
||||
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||||
```
|
||||
|
||||
### DDL in Transaktionen
|
||||
|
||||
- PostgreSQL erlaubt DDL **innerhalb** von Transaktionen und kann diese zurückrollen:
|
||||
```sql
|
||||
BEGIN;
|
||||
CREATE TABLE test (id int);
|
||||
ROLLBACK; -- Tabelle existiert danach nicht
|
||||
```
|
||||
|
||||
- Standard-Clients (z.B. `psql`) arbeiten mit Auto-Commit = ON, aber du kannst das Verhalten steuern.
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- VACUUM/Autovacuum verstehen: wann notwendig, wie überwachen, wie konfigurieren.
|
||||
- Unterschiedliche Semantik von Isolation Levels im Detail prüfen (z.B. bei Portierung von Code, der sich auf Oracle-Sperrverhalten verlässt).
|
||||
|
||||
---
|
||||
|
||||
## 8. Indizes & Partitionierung
|
||||
|
||||
### Index-Typen
|
||||
|
||||
PostgreSQL bietet mehr verschiedene Index-Methoden:
|
||||
|
||||
- `btree` (Standard)
|
||||
- `hash` (seltener)
|
||||
- `GIN` (für Volltext, JSONB-Keys, Arrays)
|
||||
- `GiST` (Geodaten, Range-Typen)
|
||||
- `BRIN` (große append-only Tabellen, z.B. Logs)
|
||||
|
||||
Zusätzlich:
|
||||
|
||||
- **Expression Indexes**:
|
||||
```sql
|
||||
CREATE INDEX idx_lower_name ON kunde (lower(name));
|
||||
```
|
||||
|
||||
- **Partial Indexes**:
|
||||
```sql
|
||||
CREATE INDEX idx_active_kunden ON kunde (id) WHERE aktiv = true;
|
||||
```
|
||||
|
||||
### Partitionierung
|
||||
|
||||
Neuere PostgreSQL-Versionen haben native Partitionierung (Range, List, Hash). Unterschied zu Oracle:
|
||||
|
||||
- Implementierung anders, kein identisches Interface.
|
||||
- Viele Operationen laufen „Partition-transparent“, aber es gibt noch Ecken (z.B. bestimmte DDL-Operationen).
|
||||
|
||||
```sql
|
||||
CREATE TABLE messwerte (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||
ts timestamptz NOT NULL,
|
||||
wert numeric
|
||||
) PARTITION BY RANGE (ts);
|
||||
|
||||
CREATE TABLE messwerte_2024 PARTITION OF messwerte
|
||||
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
|
||||
```
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Eignung und Grenzen von GIN/GiST verstehen, wenn du JSONB oder Volltext planst.
|
||||
- Partitionierungsstrategie neu durchdenken, statt 1:1 die Oracle-Logik zu kopieren.
|
||||
|
||||
---
|
||||
|
||||
## 9. Materialized Views
|
||||
|
||||
PostgreSQL hat Materialized Views, aber:
|
||||
|
||||
- Kein `ON COMMIT REFRESH`.
|
||||
- Refresh ist explizit:
|
||||
```sql
|
||||
REFRESH MATERIALIZED VIEW my_mv;
|
||||
```
|
||||
- Optional mit `CONCURRENTLY` (mit Einschränkungen), um Downtime zu minimieren.
|
||||
|
||||
**Lernpunkt:** Wenn du in Oracle stark auf automatische Refresh-Mechanismen setzt, brauchst du in PostgreSQL einen eigenen Refresh-Workflow (z.B. Cron, Scheduler, Applikationslogik).
|
||||
|
||||
---
|
||||
|
||||
## 10. Rechte & Rollenmodell
|
||||
|
||||
Ähnlich, aber mit eigenen Begriffen/Details:
|
||||
|
||||
- Nur **Rollen** (kein getrenntes Konzept von „User“ und „Role“ wie in Oracle; ein User ist eine Login-fähige Rolle).
|
||||
- Rechte auf Objekte (`GRANT SELECT ON table TO role;` etc.)
|
||||
- Kein systemweites `PUBLIC SYNONYM`, aber es gibt das Schema `public` und das `PUBLIC`-Rolle/Privileges-Konzept.
|
||||
|
||||
```sql
|
||||
CREATE ROLE app_user LOGIN PASSWORD '...';
|
||||
GRANT CONNECT ON DATABASE mydb TO app_user;
|
||||
GRANT USAGE ON SCHEMA app TO app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
|
||||
```
|
||||
|
||||
**Lernpunkt:** Mapping deiner bisherigen Oracle-Rollen/Profiles auf PostgreSQL-Rollen und -Rechte.
|
||||
|
||||
---
|
||||
|
||||
## 11. Administration, Tools & Ökosystem
|
||||
|
||||
### Wichtige Tools
|
||||
|
||||
- `psql` (Kommandozeile, sehr mächtig)
|
||||
- Admin-Tools: `pgAdmin`, `DBeaver`, `DataGrip` etc.
|
||||
- Verbindungs-Pooler (wichtig, da PostgreSQL pro Verbindung einen Prozess startet):
|
||||
- `pgbouncer`, `pgpool-II`
|
||||
|
||||
### Wichtige Admin-Konzepte
|
||||
|
||||
- **Konfiguration** über `postgresql.conf` und `ALTER SYSTEM`:
|
||||
z.B. `shared_buffers`, `work_mem`, `maintenance_work_mem`, `max_connections`.
|
||||
- **VACUUM/ANALYZE**:
|
||||
- `VACUUM` räumt auf.
|
||||
- `ANALYZE` aktualisiert Statistiken.
|
||||
- **EXPLAIN/EXPLAIN ANALYZE**: zwingend für Performance-Tuning.
|
||||
|
||||
```sql
|
||||
EXPLAIN ANALYZE
|
||||
SELECT ...
|
||||
FROM ...
|
||||
WHERE ...;
|
||||
```
|
||||
|
||||
- **Backup/Recovery**:
|
||||
- Logische Backups: `pg_dump`, `pg_dumpall`.
|
||||
- Physische Backups & PITR: Base-Backups + WAL-Archiving (oder Tools wie `pgBackRest`).
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Verbindungspooling ist viel wichtiger als in Oracle.
|
||||
- Autovacuum-Monitoring (z.B. via `pg_stat_*`-Views) und Basiskonfiguration.
|
||||
|
||||
---
|
||||
|
||||
## 12. Typische Stolpersteine beim Umstieg
|
||||
|
||||
1. **Fehlende Synonyme** → Lösung über `search_path`, Views, klare Schema-Strategie.
|
||||
2. **Paketkonzept fehlt** → Namespacing neu denken.
|
||||
3. **Materialized View Refresh** → eigene Scheduler-Logik.
|
||||
4. **ROWNUM/`dual`** → an `LIMIT`/`OFFSET`, Window Functions, `SELECT 1;` gewöhnen.
|
||||
5. **Datenbanklinks (`DBLINK`)** → Extension `dblink` oder `postgres_fdw` (Foreign Data Wrapper) verwenden.
|
||||
6. **Autoincrement** → `IDENTITY`/`SERIAL` + `currval()/nextval()`-Verwendung lernen.
|
||||
7. **Optimizer-Verhalten** → Statistiken, Parameter und Query-Pläne sind anders; alte „Index-Hints-Gewohnheiten“ greifen nicht 1:1.
|
||||
|
||||
---
|
||||
|
||||
## 13. Sinnvolle Lernreihenfolge
|
||||
|
||||
1. Basiskonzepte:
|
||||
- Schemata + `search_path`
|
||||
- Datentyp-Mapping (NUMBER, DATE, CLOB/BLOB → NUMERIC, TIMESTAMP, TEXT/BYTEA)
|
||||
- Sequences & Identity
|
||||
2. SQL-Dialekt:
|
||||
- Funktionale Unterschiede (`DECODE`, `NVL`, `ROWNUM`, `dual`, Subqueries)
|
||||
3. Server-seitige Logik:
|
||||
- PL/pgSQL, Unterschiede zu PL/SQL, kein Package-Konzept
|
||||
4. MVCC & Performance:
|
||||
- VACUUM/Autovacuum, EXPLAIN ANALYZE, Index-Typen
|
||||
5. Admin & Betrieb:
|
||||
- Rollen, Backups, Konfiguration, Verbindungspooling
|
||||
6. Spezielle Features:
|
||||
- JSONB, Arrays, GIN-Indizes, Partitionierung, FDWs
|
||||
|
||||
Wenn du magst, kannst du mir ein paar typische Oracle-Features nennen, die du intensiv nutzt (z.B. bestimmte Partitionierungsarten, Advanced Queuing, bestimmte PL/SQL-Patterns). Dann kann ich dir eine gezielte „Mapping-Tabelle“ für genau diese Themen in PostgreSQL machen.
|
||||
@@ -0,0 +1,384 @@
|
||||
#datenbank
|
||||
Kontext: Datenbanken
|
||||
|
||||
---
|
||||
ACID-Transaktionen sind ein zentrales Konzept in der Datenbankwelt – vor allem dort, wo Daten „kritisch“ sind (z.B. Banking, Buchhaltung, Bestellungen). Ich führe dich Schritt für Schritt ein, ohne Vorwissen vorauszusetzen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee: Was ist eine Transaktion?
|
||||
|
||||
Stell dir eine Transaktion als **logische Einheit von mehreren Datenbankoperationen** vor, die entweder **komplett** oder **gar nicht** ausgeführt werden soll.
|
||||
|
||||
Beispiel (Banküberweisung):
|
||||
- Konto A: 100 € → 80 € (10 € werden abgebucht)
|
||||
- Konto B: 50 € → 70 € (10 € werden gutgeschrieben)
|
||||
|
||||
Diese beiden Schritte gehören zusammen:
|
||||
- Es darf nicht passieren, dass bei A 10 € abgebucht werden, ohne dass sie bei B ankommen.
|
||||
- Oder dass B 10 € bekommt, ohne dass sie bei A abgezogen werden.
|
||||
|
||||
Beide Aktionen zusammen bilden eine **Transaktion**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Was bedeutet ACID?
|
||||
|
||||
**ACID** steht für vier Eigenschaften, die Transaktionen in klassischen relationalen Datenbanken gewährleisten sollen:
|
||||
|
||||
1. **A – Atomicity (Atomarität)**
|
||||
2. **C – Consistency (Konsistenz)**
|
||||
3. **I – Isolation**
|
||||
4. **D – Durability (Dauerhaftigkeit)**
|
||||
|
||||
Ich erkläre jede mit einfachen Beispielen.
|
||||
|
||||
---
|
||||
|
||||
### 2.1 Atomicity (Atomarität)
|
||||
|
||||
**Definition:**
|
||||
Eine Transaktion wird **entweder vollständig oder gar nicht** ausgeführt. Es gibt keinen „halben“ Zustand.
|
||||
|
||||
Beispiel:
|
||||
Wieder unsere Banküberweisung:
|
||||
1. 10 € von Konto A abbuchen
|
||||
2. 10 € Konto B gutschreiben
|
||||
|
||||
Passiert in der Mitte ein Fehler (z.B. Stromausfall nach Schritt 1), sorgt Atomicity dafür:
|
||||
- Entweder werden **beide** Schritte ausgeführt
|
||||
- oder **beide rückgängig gemacht** (Rollback)
|
||||
|
||||
Es darf nicht der Zustand entstehen:
|
||||
- Konto A: -10 €
|
||||
- Konto B: unverändert
|
||||
|
||||
Das wäre ein inkonsistenter Zustand – Atomicity verhindert das.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Consistency (Konsistenz)
|
||||
|
||||
**Definition (aus Sicht der Datenbankregeln):**
|
||||
Eine Transaktion bringt die Datenbank von einem **gültigen Zustand** in einen **anderen gültigen Zustand**, gemäß den definierten Regeln (Constraints, Geschäftsregeln).
|
||||
|
||||
Wichtig: Konsistenz bezieht sich nicht nur auf „richtige Daten“, sondern auf die **Einhaltung von Regeln**, z.B.:
|
||||
|
||||
- Fremdschlüssel-Beziehungen (z.B. jede Bestellung gehört zu einem existierenden Kunden)
|
||||
- Eindeutigkeit (z.B. E-Mail-Adressen sind eindeutig)
|
||||
- Wertebereiche (z.B. Kontostand darf nicht negativ sein, wenn das System das verbietet)
|
||||
|
||||
Beispiel:
|
||||
- Regel: Kontostand darf nicht unter 0 fallen.
|
||||
- Transaktion: Konto mit 5 € soll 10 € abbuchen.
|
||||
- Datenbank oder Geschäftslogik verhindern, dass diese Transaktion „erfolgreich bestätigt“ wird.
|
||||
- Nach der Transaktion ist die Datenbank immer noch in einem Zustand, in dem alle Regeln gelten.
|
||||
|
||||
Konsistenz sagt also:
|
||||
„Wenn du eine Transaktion beginnst, die gültige Daten voraussetzt, dann endet sie (falls sie commitet wird) wieder mit gültigen Daten.“
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Isolation
|
||||
|
||||
**Definition:**
|
||||
Parallele Transaktionen **beeinflussen sich nicht gegenseitig** in einer Art und Weise, die zu inkonsistenten Ergebnissen führt.
|
||||
Jede Transaktion „fühlt sich so an“, als wäre sie die einzige, die gerade läuft.
|
||||
|
||||
Isolation wird in der Praxis über **Isolation Levels** gesteuert (z.B. READ COMMITTED, REPEATABLE READ, SERIALIZABLE), aber fürs Grundverständnis reicht:
|
||||
|
||||
Beispiel (gleichzeitige Transaktionen):
|
||||
|
||||
- T1: Bucht 10 € von Konto A zu Konto B um.
|
||||
- T2: Prüft den Kontostand von Konto A für eine Kreditprüfung.
|
||||
|
||||
Ohne Isolation könnte T2 gerade dann den Kontostand lesen, wenn T1 mitten in der Überweisung ist (z.B. A: 90 €, B: noch nicht aktualisiert).
|
||||
Je nach Isolationsebene verhindert das System:
|
||||
- „schmutzige“ Reads (lesen von unbestätigten Zwischenständen),
|
||||
- instabile Wiederholungen (derselbe SELECT liefert in einer Transaktion plötzlich andere Ergebnisse),
|
||||
- Phantom Reads (neue Datensätze tauchen „plötzlich“ in einer laufenden Transaktion auf).
|
||||
|
||||
Einfach gesagt: Isolation schützt davor, dass sich parallele Transaktionen gegenseitig „in die Quere kommen“.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Durability (Dauerhaftigkeit)
|
||||
|
||||
**Definition:**
|
||||
Wenn eine Transaktion **erfolgreich abgeschlossen** (committed) ist, bleiben die Änderungen **dauerhaft** gespeichert – auch bei:
|
||||
|
||||
- Stromausfall
|
||||
- Absturz des Servers
|
||||
- Neustart der Datenbank
|
||||
|
||||
Technisch wird das oft über:
|
||||
- Write-Ahead-Log (Transaktionslog),
|
||||
- Journaling,
|
||||
- redundante Speicherung
|
||||
umgesetzt.
|
||||
|
||||
Beispiel:
|
||||
- Überweisung wurde committed.
|
||||
- Direkt danach fällt der Strom aus.
|
||||
- Nach Neustart der Datenbank sind die Kontostände **so**, wie sie nach der Überweisung sein sollen.
|
||||
|
||||
Keine „zufällige Rücksetzung“ auf den Stand davor.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 Transaktion vs. einzelne SQL-Operation
|
||||
|
||||
- Eine **Transaktion** kann aus einer oder mehreren SQL-Anweisungen (z.B. mehreren INSERT, UPDATE, DELETE) bestehen.
|
||||
- Ohne explizite Transaktion wird oft jede Anweisung automatisch als eigene, kleine Transaktion behandelt (Auto-Commit-Modus).
|
||||
|
||||
Beispiel:
|
||||
- Zwei separate UPDATE-Befehle ohne Transaktion:
|
||||
- Es kann passieren, dass nur der erste erfolgreich ist und der zweite fehlschlägt – dann hast du einen halben Zustand.
|
||||
- In einer Transaktion:
|
||||
- Beides wird zusammen behandelt – im Fehlerfall wird alles zurückgerollt.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 ACID vs. BASE (in verteilten / NoSQL-Systemen)
|
||||
|
||||
In vielen verteilten Systemen (z.B. große NoSQL-Datenbanken) wird statt ACID eher das Prinzip **BASE** verfolgt:
|
||||
|
||||
- **B**asically **A**vailable – Das System ist grundsätzlich verfügbar.
|
||||
- **S**oft-state – Der Zustand kann sich ändern, auch ohne explizite Transaktion.
|
||||
- **E**ventual consistency – Daten werden **irgendwann konsistent**, nicht unbedingt sofort.
|
||||
|
||||
Vergleich:
|
||||
- ACID: Strenge Garantien, dafür manchmal langsamer oder schwerer skalierbar.
|
||||
- BASE: Weniger strenge Garantien, dafür oft leicht horizontal skalierbar (über viele Server verteilt).
|
||||
|
||||
Beispiel:
|
||||
- Social-Media-Likes:
|
||||
- Es ist ok, wenn du für ein paar Sekunden oder Minuten leicht unterschiedliche Like-Zahlen auf verschiedenen Geräten siehst.
|
||||
- Bankkonto:
|
||||
- Es ist **nicht** ok, wenn der Kontostand „irgendwann mal“ stimmt. Hier will man ACID.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Konsistenz (ACID) vs. Konsistenz (CAP-Theorem)
|
||||
|
||||
Der Begriff „Konsistenz“ wird auch im **CAP-Theorem** verwendet (bei verteilten Systemen). Dort bedeutet er etwas anderes:
|
||||
|
||||
- ACID-Konsistenz: Einhaltung von Datenbankregeln (Constraints, Geschäftslogik).
|
||||
- CAP-Konsistenz: Alle Knoten eines verteilten Systems sehen dieselben Daten zur selben Zeit.
|
||||
|
||||
Es ist wichtig, diese beiden Bedeutungen nicht zu verwechseln.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme lösen ACID-Transaktionen?
|
||||
|
||||
### 4.1 Vermeidung halbfertiger Zustände
|
||||
|
||||
Ohne Atomicity:
|
||||
- Stromausfall mitten in einer Update-Sequenz → Daten inkonsistent.
|
||||
|
||||
Mit ACID:
|
||||
- Entweder alles oder nichts → Daten bleiben konsistent.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Schutz vor parallelen Zugriffskonflikten
|
||||
|
||||
Typische Probleme ohne Isolation:
|
||||
|
||||
1. **Lost Update (verlorenes Update)**
|
||||
- Zwei Benutzer lesen denselben Datenstand, ändern ihn unabhängig und speichern beide.
|
||||
- Die zweite Speicherung überschreibt die erste, ohne es zu merken.
|
||||
|
||||
Beispiel:
|
||||
- Lagerbestand: 10 Stück
|
||||
- Benutzer A: Reserviert 3 (denkt: 10 → 7)
|
||||
- Benutzer B: Reserviert 4 (denkt: 10 → 6)
|
||||
- Ohne Schutz: Endergebnis könnte 6 sein (Reservierung von A geht „verloren“).
|
||||
- Mit Isolation/Locks: Das System stellt sicher, dass die Updates korrekt nacheinander verarbeitet werden.
|
||||
|
||||
2. **Dirty Read (schmutziges Lesen)**
|
||||
- Eine Transaktion liest Änderungen, die eine andere Transaktion noch gar nicht committed hat.
|
||||
- Wenn die zweite Transaktion zurückgerollt wird, hat man auf falschen Daten gearbeitet.
|
||||
|
||||
3. **Non-repeatable Read & Phantom Read**
|
||||
- Eine Transaktion liest dieselben Daten mehrmals, aber bekommt unterschiedliche Ergebnisse, weil andere Transaktionen dazwischen geschrieben haben.
|
||||
- Oder es tauchen plötzlich zusätzliche/fehlende Zeilen auf.
|
||||
|
||||
ACID + passende Isolationsebene reduziert oder verhindert solche Probleme.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Sicherung kritischer Geschäftsprozesse
|
||||
|
||||
Wo ACID typisch ist:
|
||||
|
||||
- Bank- und Finanzsysteme
|
||||
- Flugsitz-Reservierungssysteme
|
||||
- Warenwirtschaft / Bestellabwicklung
|
||||
- Ticketverkauf (Konzert, Bahn, Flug)
|
||||
|
||||
Beispiel Tickets:
|
||||
- Nur 1 Ticket übrig.
|
||||
- 2 Leute versuchen gleichzeitig, es zu kaufen.
|
||||
- ACID-Transaktionen sorgen dafür, dass am Ende nur eine Person das Ticket bekommt, nicht beide.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Herausforderungen bringen ACID-Transaktionen mit sich?
|
||||
|
||||
ACID klingt perfekt, hat aber trade-offs.
|
||||
|
||||
### 5.1 Performance und Skalierbarkeit
|
||||
|
||||
- Starke Isolation und Konsistenz können **langsamer** sein, weil:
|
||||
- Locks (Sperren) auf Zeilen/Tabellen gesetzt werden,
|
||||
- viele Logs geschrieben werden müssen,
|
||||
- parallele Zugriffe begrenzt werden.
|
||||
|
||||
Beispiel:
|
||||
- In einem stark frequentierten Online-Shop könnten lange laufende Transaktionen zu Warteschlangen führen:
|
||||
- Kunden müssen warten, bevor ihre Updates durchgeführt werden können.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Deadlocks (Verklemmungen)
|
||||
|
||||
Wenn mehrere Transaktionen sich gegenseitig sperren, kann es zu einem **Deadlock** kommen:
|
||||
|
||||
Beispiel:
|
||||
|
||||
- T1:
|
||||
- Sperrt Zeile X
|
||||
- Will dann Zeile Y sperren (die aber schon von T2 gesperrt ist)
|
||||
- T2:
|
||||
- Sperrt Zeile Y
|
||||
- Will dann Zeile X sperren (die aber schon von T1 gesperrt ist)
|
||||
|
||||
Beide warten aufeinander → Niemand kommt weiter.
|
||||
|
||||
Die Datenbank muss Deadlocks erkennen und eine Transaktion abbrechen (Rollback), damit sich das System wieder erholen kann.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Lange laufende Transaktionen
|
||||
|
||||
- Je länger eine Transaktion läuft, desto länger hält sie Sperren.
|
||||
- Das kann viele andere Benutzer blockieren.
|
||||
|
||||
Beispiel:
|
||||
- Ein Report, der mehrere Minuten läuft, wird innerhalb einer Transaktion ausgeführt.
|
||||
- In dieser Zeit können andere nicht ordentlich auf dieselben Daten zugreifen (je nach Isolationsebene).
|
||||
|
||||
Deshalb:
|
||||
In der Praxis versucht man, Transaktionen so **kurz wie möglich** zu halten.
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Verteilte Transaktionen (über mehrere Systeme)
|
||||
|
||||
Wenn eine Transaktion **über mehrere Datenbanken oder Services** geht, wird es komplex:
|
||||
|
||||
Beispiel:
|
||||
- Bestellsystem:
|
||||
- Reserviert Ware in Lager-Datenbank A
|
||||
- Erstellt Rechnung in Finanz-Datenbank B
|
||||
|
||||
Um ACID über beide Systeme zu garantieren, braucht man z.B.:
|
||||
- 2-Phase-Commit (2PC),
|
||||
- spezielle Transaktionskoordinatoren.
|
||||
|
||||
Probleme:
|
||||
- Komplex, fehleranfällig,
|
||||
- schlechte Performance,
|
||||
- schlechte Skalierbarkeit in großen, verteilten Systemen.
|
||||
|
||||
Darum verzichten moderne Microservice-Architekturen oft auf verteilte ACID-Transaktionen und nutzen stattdessen:
|
||||
- Eventual Consistency,
|
||||
- Sagas (geschäftliche, verteilte Abläufe mit Kompensationsaktionen).
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Komplexität in der Anwendungslogik
|
||||
|
||||
- Wenn Entwickler die Eigenschaften von Isolation, Locks, Deadlocks etc. nicht verstehen, können:
|
||||
- unerwartete Blockaden,
|
||||
- merkwürdige Nebenwirkungen,
|
||||
- Performanceprobleme
|
||||
auftreten.
|
||||
|
||||
Deshalb ist ein grundlegendes Verständnis von ACID und Transaktionen auch für Anwendungsentwickler wichtig, nicht nur für Datenbankadministratoren.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele im Überblick
|
||||
|
||||
### Beispiel 1: Bestellprozess im Online-Shop
|
||||
|
||||
Schritte, die typischerweise in einer Transaktion laufen können:
|
||||
|
||||
1. Kundenbestellung speichern.
|
||||
2. Lagerbestand reduzieren.
|
||||
3. Reservierte Waren markieren.
|
||||
4. Zahlstatus erfassen (z.B. „Bezahlung ausstehend“).
|
||||
|
||||
Mit ACID:
|
||||
- Wenn in Schritt 2 der Lagerbestand negativ würde → Transaktion fehlschlägt → nichts wird dauerhaft gespeichert.
|
||||
- Kein Zustand „Bestellung existiert, aber Lagerbestand stimmt nicht“.
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 2: Benutzerregistrierung
|
||||
|
||||
Schritte:
|
||||
|
||||
1. Benutzerkonto anlegen (INSERT in `users`).
|
||||
2. Authentifizierungsdaten anlegen (INSERT in `credentials`).
|
||||
3. Willkommensguthaben in ein Bonuskonto einzahlen (INSERT in `bonus_accounts`).
|
||||
|
||||
Mit ACID:
|
||||
- Entweder alle drei Inserts werden erfolgreich committed
|
||||
- oder keiner.
|
||||
- Kein Benutzer ohne Credentials, kein Bonuskonto ohne Benutzer.
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 3: Rechnungsstellung
|
||||
|
||||
Schritte:
|
||||
|
||||
1. Rechnung anlegen.
|
||||
2. Rechnungsposten anlegen.
|
||||
3. Offenen Posten im Finanzsystem erzeugen.
|
||||
|
||||
Mit ACID:
|
||||
- Wenn das Anlegen der Rechnungsposten fehlschlägt, wird auch die Rechnung selbst zurückgerollt.
|
||||
- Es gibt keine „leere Rechnung“.
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusammenfassung
|
||||
|
||||
- **Transaktionen** fassen mehrere Operationen zu einer Einheit zusammen.
|
||||
- **ACID**-Eigenschaften sorgen dafür, dass diese Einheiten:
|
||||
- **A**tomar sind (alles oder nichts),
|
||||
- **C**onsistent bleiben (Regeln bleiben gültig),
|
||||
- **I**soliert laufen (parallele Transaktionen stören sich nicht „unsichtbar“),
|
||||
- **D**auerhaft gespeichert werden (Commit ist endgültig, auch bei Abstürzen).
|
||||
|
||||
**Sie lösen vor allem:**
|
||||
- Teilweise Updates,
|
||||
- Inkonsistenzen durch Fehler,
|
||||
- Probleme durch parallele Zugriffe.
|
||||
|
||||
**Herausforderungen sind:**
|
||||
- Performance,
|
||||
- Skalierung,
|
||||
- Deadlocks,
|
||||
- Komplexität (vor allem in verteilten Systemen).
|
||||
|
||||
Wenn du möchtest, können wir als nächsten Schritt:
|
||||
- konkrete SQL-Beispiele (z.B. in PostgreSQL oder MySQL) durchgehen, oder
|
||||
- tiefer in Isolation Levels (READ COMMITTED, REPEATABLE READ, SERIALIZABLE) einsteigen.
|
||||
+549
@@ -0,0 +1,549 @@
|
||||
#datenbank
|
||||
Kontext: Datenbanken
|
||||
|
||||
= ==Common Table Expression==
|
||||
|
||||
>`WITH`-Block in SQL-Abfrage
|
||||
|
||||
➡️[[#Zusammenfassung]]
|
||||
|
||||
---
|
||||
Im Folgenden bekommst du eine umfassende, aber einsteigerfreundliche Einführung in CTEs („Common Table Expressions“) im Kontext von Datenbanken – mit Definition, Abgrenzung, Nutzen, Herausforderungen und praxisnahen Beispielen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee: Was ist eine CTE?
|
||||
|
||||
**CTE** steht für **Common Table Expression**.
|
||||
Vereinfacht gesagt ist eine CTE:
|
||||
|
||||
> Eine **temporäre, benannte Ergebnismenge**, die du in einer SQL-Abfrage definierst und im Anschluss in derselben Abfrage wie eine Tabelle verwenden kannst.
|
||||
|
||||
Man kann sich das vorstellen wie:
|
||||
|
||||
- „Ich speichere mir ein Zwischenergebnis unter einem Namen…“
|
||||
- „…und benutze dieses Zwischenergebnis dann in der eigentlichen Anfrage.“
|
||||
|
||||
Grundform:
|
||||
|
||||
```sql
|
||||
WITH name_der_cte AS (
|
||||
-- irgendeine SELECT-Abfrage
|
||||
SELECT ...
|
||||
FROM ...
|
||||
WHERE ...
|
||||
)
|
||||
SELECT *
|
||||
FROM name_der_cte;
|
||||
```
|
||||
|
||||
Wichtig:
|
||||
|
||||
- Die CTE existiert **nur für diese eine Abfrage**.
|
||||
- Sie wird **innerhalb der Abfrage** definiert und direkt **danach** genutzt.
|
||||
- Man kann CTEs **mehrfach referenzieren**, als wären es echte Tabellen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ein einfaches Beispiel
|
||||
|
||||
Stell dir vor, du hast eine Tabelle `bestellungen`:
|
||||
|
||||
- `id`
|
||||
- `kunde_id`
|
||||
- `betrag`
|
||||
- `datum`
|
||||
|
||||
Du möchtest:
|
||||
|
||||
1. Alle Bestellungen des Jahres 2024 herausfiltern.
|
||||
2. Dann auf Basis dieser gefilterten Daten den Gesamtumsatz pro Kunde berechnen.
|
||||
|
||||
Ohne CTE könnte man das mit einer verschachtelten Abfrage lösen; mit CTE sieht es lesbarer aus:
|
||||
|
||||
```sql
|
||||
WITH bestellungen_2024 AS (
|
||||
SELECT *
|
||||
FROM bestellungen
|
||||
WHERE datum >= '2024-01-01'
|
||||
AND datum < '2025-01-01'
|
||||
)
|
||||
SELECT
|
||||
kunde_id,
|
||||
SUM(betrag) AS umsatz_2024
|
||||
FROM bestellungen_2024
|
||||
GROUP BY kunde_id;
|
||||
```
|
||||
|
||||
Die CTE `bestellungen_2024` ist hier:
|
||||
„Alle Bestellungen aus 2024“, und wird anschließend in der Hauptabfrage verwendet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu ähnlichen oder verwandten Begriffen
|
||||
|
||||
### 3.1 CTE vs. Unterabfrage (Subquery / Derived Table)
|
||||
|
||||
**Unterabfrage**: Eine Abfrage innerhalb einer anderen Abfrage, z. B.:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
kunde_id,
|
||||
SUM(betrag) AS umsatz_2024
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM bestellungen
|
||||
WHERE datum >= '2024-01-01'
|
||||
AND datum < '2025-01-01'
|
||||
) AS b2024
|
||||
GROUP BY kunde_id;
|
||||
```
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- **CTE**: Wird am Anfang mit `WITH` definiert, trägt einen Namen und kann **mehrfach** benutzt werden.
|
||||
- **Subquery**: Steht direkt im `FROM` oder `WHERE`, ist meist **anonymer** und schwerer zu lesen, vor allem bei komplexen Konstrukten.
|
||||
|
||||
Funktional können CTEs und Subqueries oft das Gleiche – CTEs verbessern eher **Struktur und Lesbarkeit**.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 CTE vs. View (Sicht)
|
||||
|
||||
**View** (Sicht) ist wie eine **gespeicherte Abfrage** in der Datenbank:
|
||||
|
||||
```sql
|
||||
CREATE VIEW bestellungen_2024 AS
|
||||
SELECT *
|
||||
FROM bestellungen
|
||||
WHERE datum >= '2024-01-01'
|
||||
AND datum < '2025-01-01';
|
||||
```
|
||||
|
||||
Dann kannst du schreiben:
|
||||
|
||||
```sql
|
||||
SELECT kunde_id, SUM(betrag)
|
||||
FROM bestellungen_2024
|
||||
GROUP BY kunde_id;
|
||||
```
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- **View**:
|
||||
- Wird **dauerhaft** in der Datenbank definiert.
|
||||
- Hat einen Namen, kann von **vielen Abfragen** und auch anderen Nutzer:innen verwendet werden.
|
||||
- Ändert sich nur über `CREATE OR REPLACE VIEW` / `ALTER VIEW`.
|
||||
- **CTE**:
|
||||
- Gilt **nur für eine einzige Abfrage**.
|
||||
- Ist daher ideal für **einmalige** oder **sehr spezialisierte** Zwischenschritte.
|
||||
- Erfordert keine Rechte zum Erstellen von Objekten in der Datenbank (kein `CREATE VIEW`).
|
||||
|
||||
Kurz:
|
||||
Views = dauerhafte, wiederverwendbare Bausteine.
|
||||
CTEs = temporäre, einmalige Bausteine innerhalb einer Abfrage.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 CTE vs. temporäre Tabelle
|
||||
|
||||
Viele Datenbanken kennen **temporäre Tabellen**, z. B. `#temp_tab` in SQL Server oder `CREATE TEMP TABLE` in PostgreSQL:
|
||||
|
||||
```sql
|
||||
CREATE TEMP TABLE bestellungen_2024 AS
|
||||
SELECT *
|
||||
FROM bestellungen
|
||||
WHERE datum >= '2024-01-01'
|
||||
AND datum < '2025-01-01';
|
||||
|
||||
SELECT kunde_id, SUM(betrag)
|
||||
FROM bestellungen_2024
|
||||
GROUP BY kunde_id;
|
||||
```
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- **Temporäre Tabelle**:
|
||||
- Wird physisch (zumindest logisch) in der Datenbank angelegt.
|
||||
- Existiert für die Dauer einer Session oder Transaktion.
|
||||
- Kann **indiziert** werden (Index hinzufügen) und so bei großen Datenmengen Performancevorteile bringen.
|
||||
- **CTE**:
|
||||
- Keine echte Tabelle, eher eine „logische Abfragekomponente“.
|
||||
- Keine eigenen Indexe.
|
||||
- Gilt nur innerhalb einer Abfrage.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 CTE vs. Stored Procedure / Function
|
||||
|
||||
**Stored Procedures** und **Functions** sind Programmierbausteine auf Datenbankseite, z. B.:
|
||||
|
||||
- Prozeduren: führen mehrere Schritte, ggf. mit Kontrollstrukturen aus (IF, WHILE,…).
|
||||
- Funktionen: geben einen Wert oder eine Tabelle zurück.
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- CTE ist **Teil einer einzelnen SQL-Select/Insert/Update/Delete-Abfrage**.
|
||||
- Stored Proc / Function ist **Code**, den man **speichert, versioniert und immer wieder aufrufen** kann.
|
||||
|
||||
CTEs können innerhalb von Stored Procedures verwendet werden – sie sind eher Bausteine **auf Abfrage-Ebene**, nicht auf Programm-Ebene.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme werden durch CTEs gelöst?
|
||||
|
||||
### 4.1 Bessere Lesbarkeit und Struktur
|
||||
|
||||
Statt eine riesige, komplexe Abfrage mit vielen verschachtelten Unterabfragen zu schreiben, kann man sich die Abfrage in **logische Schritte** zerlegen:
|
||||
|
||||
1. `WITH schritt1 AS (...)`
|
||||
2. `, schritt2 AS (...)`
|
||||
3. `SELECT ... FROM schritt2 ...`
|
||||
|
||||
Beispiel:
|
||||
Du möchtest erst „aktive Kunden“ bestimmen und dann nur deren Bestellungen summieren:
|
||||
|
||||
```sql
|
||||
WITH aktive_kunden AS (
|
||||
SELECT id, name
|
||||
FROM kunden
|
||||
WHERE status = 'aktiv'
|
||||
),
|
||||
bestellungen_aktive_kunden AS (
|
||||
SELECT
|
||||
b.kunde_id,
|
||||
SUM(b.betrag) AS umsatz
|
||||
FROM bestellungen b
|
||||
JOIN aktive_kunden k ON k.id = b.kunde_id
|
||||
GROUP BY b.kunde_id
|
||||
)
|
||||
SELECT
|
||||
k.name,
|
||||
bak.umsatz
|
||||
FROM bestellungen_aktive_kunden bak
|
||||
JOIN aktive_kunden k ON k.id = bak.kunde_id;
|
||||
```
|
||||
|
||||
Jeder CTE beschreibt einen **klaren Teilschritt**, das erleichtert Verstehen und Warten des Codes.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Wiederverwendung innerhalb einer Abfrage
|
||||
|
||||
Oft brauchst du ein bestimmtes Zwischenergebnis **mehrmals in derselben Abfrage**.
|
||||
Ohne CTE müsstest du:
|
||||
|
||||
- Dieselbe Unterabfrage mehrfach schreiben (redundant, fehleranfällig)
|
||||
- Oder du machst Temp-Tabellen / Views, die aber mehr Setup erfordern.
|
||||
|
||||
Mit CTE definierst du das einmal und verwendest es mehrfach:
|
||||
|
||||
```sql
|
||||
WITH bestellungen_2024 AS (
|
||||
SELECT *
|
||||
FROM bestellungen
|
||||
WHERE datum >= '2024-01-01'
|
||||
AND datum < '2025-01-01'
|
||||
)
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM bestellungen_2024) AS anzahl_gesamt,
|
||||
(SELECT SUM(betrag) FROM bestellungen_2024) AS umsatz_gesamt;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Schrittweise Transformationen (ETL-artige Abläufe)
|
||||
|
||||
Man kann komplexe Datenverarbeitungen in **mehrere CTE-Schritte** aufteilen, z. B.:
|
||||
|
||||
1. Rohdaten bereinigen (ungültige Werte rausfiltern)
|
||||
2. Daten anreichern (Join mit Lookup-Tabellen)
|
||||
3. Aggregationen berechnen
|
||||
4. Ergebnis selektieren
|
||||
|
||||
Schema:
|
||||
|
||||
```sql
|
||||
WITH raw_data AS (
|
||||
SELECT * FROM import_tabelle
|
||||
),
|
||||
cleaned_data AS (
|
||||
SELECT *
|
||||
FROM raw_data
|
||||
WHERE wert IS NOT NULL
|
||||
),
|
||||
enriched_data AS (
|
||||
SELECT
|
||||
c.*,
|
||||
l.beschreibung
|
||||
FROM cleaned_data c
|
||||
LEFT JOIN lookup l ON c.code = l.code
|
||||
),
|
||||
aggregated AS (
|
||||
SELECT
|
||||
beschreibung,
|
||||
COUNT(*) AS anzahl,
|
||||
AVG(wert) AS durchschnitt
|
||||
FROM enriched_data
|
||||
GROUP BY beschreibung
|
||||
)
|
||||
SELECT *
|
||||
FROM aggregated
|
||||
ORDER BY anzahl DESC;
|
||||
```
|
||||
|
||||
Solche „linearen“ CTE-Ketten sind sehr hilfreich, um komplexe ETL-Schritte innerhalb einer SQL-Abfrage klar zu definieren.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Rekursive Abfragen (Hierarchien, Bäume, Graphen)
|
||||
|
||||
Ein **besonderer Typ** von CTE ist die **rekursive CTE**. Damit kann man Strukturen abfragen, die **hierarchisch** sind, z. B.:
|
||||
|
||||
- Mitarbeiter und ihre Vorgesetzten (Organigramm)
|
||||
- Kategorien und Unterkategorien (Baumstrukturen)
|
||||
- Stücklisten (Bauteil besteht aus Unterteilen, die wiederum Unterteile haben)
|
||||
- Graphen/Netzwerke mit Verbindungen
|
||||
|
||||
Grundidee:
|
||||
|
||||
- Es gibt eine **Anker-Abfrage** (Startpunkt).
|
||||
- Und eine **rekursive Abfrage**, die sich immer wieder selbst referenziert.
|
||||
|
||||
Beispiel: Mitarbeiter-Hierarchie
|
||||
Tabelle `mitarbeiter`:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `chef_id` (Verweis auf `id` in derselben Tabelle, NULL für Chef ganz oben)
|
||||
|
||||
```sql
|
||||
WITH RECURSIVE hierarchie AS (
|
||||
-- 1) Anker: Chef (oberste Ebene)
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
chef_id,
|
||||
0 AS ebene
|
||||
FROM mitarbeiter
|
||||
WHERE chef_id IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 2) Rekursiver Teil: alle Mitarbeiter, die einem bereits gefundenen Mitarbeiter unterstellt sind
|
||||
SELECT
|
||||
m.id,
|
||||
m.name,
|
||||
m.chef_id,
|
||||
h.ebene + 1 AS ebene
|
||||
FROM mitarbeiter m
|
||||
JOIN hierarchie h ON m.chef_id = h.id
|
||||
)
|
||||
SELECT *
|
||||
FROM hierarchie
|
||||
ORDER BY ebene, id;
|
||||
```
|
||||
|
||||
Ergebnis:
|
||||
Alle Mitarbeiter mit einer Spalte `ebene`, die angibt, wie weit sie vom obersten Chef entfernt sind.
|
||||
|
||||
**Ohne rekursive CTEs** wäre das sehr umständlich oder gar nicht (standardkonform) direkt in SQL möglich. Manche Systeme haben dafür eigene Syntax (z. B. `CONNECT BY` in Oracle), rekursive CTEs sind der **SQL-Standard**-Weg.
|
||||
|
||||
---
|
||||
|
||||
## 5. Weitere praxisnahe Beispiele
|
||||
|
||||
### 5.1 Alltagsszenario: „Top-Produkte pro Monat“
|
||||
|
||||
Gegeben:
|
||||
|
||||
- `verkaeufe` mit Spalten:
|
||||
- `produkt_id`
|
||||
- `datum`
|
||||
- `menge`
|
||||
- `umsatz`
|
||||
|
||||
Ziel:
|
||||
Pro Monat die **Top 3 Produkte nach Umsatz** anzeigen.
|
||||
|
||||
Ein möglicher Weg mit CTE:
|
||||
|
||||
```sql
|
||||
WITH monatliche_umsaetze AS (
|
||||
SELECT
|
||||
DATE_TRUNC('month', datum) AS monat,
|
||||
produkt_id,
|
||||
SUM(umsatz) AS umsatz_monat
|
||||
FROM verkaeufe
|
||||
GROUP BY DATE_TRUNC('month', datum), produkt_id
|
||||
),
|
||||
ranking AS (
|
||||
SELECT
|
||||
monat,
|
||||
produkt_id,
|
||||
umsatz_monat,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY monat
|
||||
ORDER BY umsatz_monat DESC
|
||||
) AS rang_im_monat
|
||||
FROM monatliche_umsaetze
|
||||
)
|
||||
SELECT
|
||||
monat,
|
||||
produkt_id,
|
||||
umsatz_monat,
|
||||
rang_im_monat
|
||||
FROM ranking
|
||||
WHERE rang_im_monat <= 3
|
||||
ORDER BY monat, rang_im_monat;
|
||||
```
|
||||
|
||||
Die Logik wird nachvollziehbar in zwei Schritte aufgeteilt:
|
||||
|
||||
1. summieren pro Monat und Produkt
|
||||
2. pro Monat ranken und Top 3 auswählen
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Kalender-CTE: fehlende Tage auffüllen
|
||||
|
||||
Manchmal hat man z. B. nur Daten für Tage, an denen Verkäufe stattfanden, möchte aber **jeden Tag im Zeitraum** sehen (auch wenn Umsatz 0 ist).
|
||||
Mit einer rekursiven CTE kann man einen Kalender erzeugen.
|
||||
|
||||
Beispiel (PostgreSQL-Syntax):
|
||||
|
||||
```sql
|
||||
WITH RECURSIVE kalender AS (
|
||||
SELECT DATE '2024-01-01' AS tag
|
||||
UNION ALL
|
||||
SELECT tag + INTERVAL '1 day'
|
||||
FROM kalender
|
||||
WHERE tag < DATE '2024-01-31'
|
||||
)
|
||||
SELECT
|
||||
k.tag,
|
||||
COALESCE(SUM(v.umsatz), 0) AS umsatz
|
||||
FROM kalender k
|
||||
LEFT JOIN verkaeufe v ON DATE(v.datum) = k.tag
|
||||
GROUP BY k.tag
|
||||
ORDER BY k.tag;
|
||||
```
|
||||
|
||||
So erhältst du für **jeden Tag im Januar 2024** einen Umsatzwert – bei Tagen ohne Verkäufe eben 0.
|
||||
|
||||
---
|
||||
|
||||
## 6. Welche Herausforderungen und Stolpersteine gibt es?
|
||||
|
||||
### 6.1 Performance (Leistungsfähigkeit)
|
||||
|
||||
CTEs sind primär ein **Lesbarkeits-Feature**, aber sie können die Performance beeinflussen – je nach Datenbank:
|
||||
|
||||
- Einige Datenbanken „materialisieren“ CTEs:
|
||||
- Das Zwischenergebnis wird wirklich berechnet und zwischengespeichert.
|
||||
- Mehrfachzugriff ist dann ggf. schneller, aber die Initialberechnung kann teurer sein.
|
||||
- Andere optimieren CTEs ähnlich wie Unterabfragen:
|
||||
- Sie werden im Optimizer „eingefaltet“, d. h. wie eine direkte Teilabfrage behandelt.
|
||||
|
||||
Typische Punkte:
|
||||
|
||||
- **Mehrfache Verwendung großer CTEs**:
|
||||
- Wenn eine CTE sehr groß ist und du mehrfach darauf zugreifst, kann das teuer werden.
|
||||
- In manchen Systemen ist dann eine temporäre Tabelle mit Index effizienter.
|
||||
- **Rekursive CTEs**:
|
||||
- Können viel CPU und Zeit brauchen, wenn die Hierarchie sehr tief oder stark verzweigt ist.
|
||||
- Es gibt oft eine maximale Rekursionstiefe (z. B. 100 oder 32767), die begrenzt, wie tief die Rekursion geht.
|
||||
|
||||
Praxis-Tipp:
|
||||
|
||||
- CTEs sind super für mittlere Komplexität und moderate Datenmengen.
|
||||
- Bei sehr großen Datenmengen und Performanceproblemen:
|
||||
- Ausführungsplan anschauen.
|
||||
- Mit Temp-Tabellen, Indizes oder Views experimentieren.
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Übermäßige Verschachtelung
|
||||
|
||||
Wenn man CTEs zu exzessiv nutzt, z. B. 20 oder mehr CTEs in einer Abfrage, wird es:
|
||||
|
||||
- schwer zu lesen
|
||||
- schwierig zu debuggen
|
||||
- komplex zu warten
|
||||
|
||||
Hier gilt:
|
||||
**Abstraktion, wo sinnvoll, aber nicht übertreiben.**
|
||||
|
||||
Manchmal ist es besser:
|
||||
|
||||
- Teile der Logik in eine View zu legen,
|
||||
- oder in eine Stored Procedure / Function,
|
||||
- oder die Logik in mehrere kleinere Abfragen aufzuteilen.
|
||||
|
||||
---
|
||||
|
||||
### 6.3 Datenbankspezifische Unterschiede
|
||||
|
||||
Nicht jede Datenbank:
|
||||
|
||||
- unterstützt CTEs gleich
|
||||
- oder hat genau dieselbe Syntax.
|
||||
|
||||
Beispiele:
|
||||
|
||||
- **PostgreSQL**: Unterstützt `WITH` und `WITH RECURSIVE`.
|
||||
- **SQL Server**: Unterstützt CTEs, Rekursion ohne extra `RECURSIVE`-Keyword.
|
||||
- **MySQL**: Ab Version 8.0 gibt es CTEs, rekursiv mit `WITH RECURSIVE`.
|
||||
- **Oracle**: Unterstützt CTEs, hatte aber historisch eigene Hierarchie-Syntax (`CONNECT BY`).
|
||||
|
||||
Wenn du in mehreren Systemen arbeitest, lohnt ein Blick in die Dokumentation der jeweiligen Datenbank.
|
||||
|
||||
---
|
||||
|
||||
### 6.4 CTEs haben keine eigenen Indizes
|
||||
|
||||
Da CTEs keine physischen Tabellen sind, kannst du:
|
||||
|
||||
- keine Indexe darauf erstellen
|
||||
- keine Statistiken direkt auf CTE-Ebene pflegen
|
||||
|
||||
D. h.:
|
||||
|
||||
- Der Optimizer muss die Abfrage so gut wie möglich auf Grundlage der zugrundeliegenden Tabellen optimieren.
|
||||
- Bei Performanceproblemen kann eine echte (temporäre) Tabelle mit Index manchmal besser sein.
|
||||
|
||||
---
|
||||
|
||||
## 7. Wann sollte man CTEs einsetzen? – Orientierung
|
||||
|
||||
**Geeignet für CTEs:**
|
||||
|
||||
- Wenn eine Abfrage **schwer lesbar** ist, weil sie viele Unterabfragen enthält.
|
||||
- Wenn du **dasselbe Zwischenergebnis mehrfach** in einer Abfrage benötigst.
|
||||
- Wenn du **schrittweise Transformationen** ausdrücken möchtest („Step 1, Step 2, …“).
|
||||
- Wenn du **Hierarchien** oder rekursive Strukturen mit SQL abbilden willst.
|
||||
|
||||
**Eher nicht ideal:**
|
||||
|
||||
- Für dauerhafte, von vielen Personen genutzte Logik → eher **Views** oder **Stored Procedures**.
|
||||
- Für sehr große Zwischenergebnisse, die oft wiederverwendet werden → evtl. **temporäre Tabellen** mit Index.
|
||||
- Wenn Performance schon kritisch ist → CTE-Einsatz testen und Ausführungspläne prüfen.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- **Definition**: CTE = „Common Table Expression“ = eine **temporäre, benannte Ergebnismenge** innerhalb einer Abfrage, definiert mit `WITH`.
|
||||
- **Nutzen**:
|
||||
- Klarere Struktur und bessere Lesbarkeit komplexer SQL-Abfragen.
|
||||
- Wiederverwendbarkeit eines Zwischenergebnisses innerhalb derselben Abfrage.
|
||||
- Möglichkeit, komplexe Transformationen sequenziell auszudrücken.
|
||||
- Unterstützung rekursiver Abfragen (Hierarchien/Bäume).
|
||||
- **Abgrenzung**:
|
||||
- Gegenüber Subqueries: funktional ähnlich, aber meist lesbarer.
|
||||
- Gegenüber Views: CTEs sind **temporär**, Views **dauerhaft**.
|
||||
- Gegenüber Temp-Tabellen: CTEs sind logisch, Temp-Tabellen physisch und indexierbar.
|
||||
- **Herausforderungen**:
|
||||
- Performanceabhängig vom Datenbanksystem und vom Aufbau der Abfrage.
|
||||
- Übermäßiger Gebrauch kann Abfragen unübersichtlich machen.
|
||||
- Rekursive CTEs können komplex und teuer werden, brauchen sorgfältige Bedingungen.
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
#datenbank
|
||||
➡️[[#Zusammenfassung]]
|
||||
|
||||
---
|
||||
MVCC („==Multi-Version Concurrency Control==“) ist ein Verfahren, mit dem Datenbanken gleichzeitige Zugriffe (Concurrency) von vielen Benutzern/Programmen steuern, ohne dass sie sich ständig gegenseitig blockieren.
|
||||
|
||||
Ich gehe Schritt für Schritt vor:
|
||||
|
||||
1. Ausgangspunkt: Was ist das Problem überhaupt?
|
||||
2. Grunddefinition von MVCC (für Einsteiger)
|
||||
3. Wie funktioniert das intern grob?
|
||||
4. Abgrenzung zu verwandten Konzepten
|
||||
5. Welche Probleme löst MVCC konkret?
|
||||
6. Welche Herausforderungen/Nachteile bringt MVCC mit sich?
|
||||
7. Praxisnahe Beispiele
|
||||
|
||||
---
|
||||
|
||||
## 1. Ausgangspunkt: Wozu brauche ich so etwas?
|
||||
|
||||
In einer typischen Anwendung greifen viele Nutzer gleichzeitig auf dieselbe Datenbank zu:
|
||||
|
||||
- Nutzer A liest Daten (z.B. Produktliste im Shop)
|
||||
- Nutzer B aktualisiert Daten (z.B. ändert Preise)
|
||||
- Nutzer C führt einen großen Report aus (z.B. tägliche Umsatzauswertung)
|
||||
|
||||
Ziel:
|
||||
Alle sollen gleichzeitig arbeiten können, **ohne Fehler** wie:
|
||||
|
||||
- „Dirty Reads“ (jemand liest halbfertige Änderungen)
|
||||
- Inkonsistente Sicht auf die Daten
|
||||
- Datenverlust, weil gleichzeitige Änderungen sich überschreiben
|
||||
- Dauerhafte Sperren und Deadlocks, die das System verlangsamen oder blockieren
|
||||
|
||||
Dazu gibt es das Konzept der **Transaktionen** (ACID):
|
||||
|
||||
- **A**tomicity: Alles oder nichts.
|
||||
- **C**onsistency: Regeln der Datenbank bleiben erfüllt.
|
||||
- **I**solation: Transaktionen beeinflussen sich möglichst wenig.
|
||||
- **D**urability: Einmal bestätigte Daten gehen nicht verloren.
|
||||
|
||||
Die Isolation ist das Kernfeld, in dem MVCC ins Spiel kommt:
|
||||
Wie sorgt man dafür, dass viele Transaktionen **gleichzeitig** laufen können, ohne sich ständig zu blockieren?
|
||||
|
||||
---
|
||||
|
||||
## 2. Grunddefinition von MVCC (für Einsteiger)
|
||||
|
||||
**MVCC (Multi-Version Concurrency Control)** bedeutet:
|
||||
|
||||
- **Jede Zeile (Row) in einer Tabelle existiert in mehreren Versionen.**
|
||||
- Jede Transaktion sieht die Daten „so, wie sie zu einem bestimmten Zeitpunkt waren“ – sie arbeitet mit einem **Snapshot** (Momentaufnahme).
|
||||
- Wenn jemand etwas **ändert**, wird **nicht** die existierende Zeile überschrieben, sondern eine **neue Version** der Zeile angelegt.
|
||||
- Für jede Transaktion gibt es Regeln, **welche Version** einer Zeile sie sehen darf.
|
||||
|
||||
Anschaulich:
|
||||
|
||||
- Stell dir eine Tabelle als Tabelle in Excel vor.
|
||||
- MVCC macht von einigen Zeilen „Kopien“, wenn sie geändert werden.
|
||||
- Du (deine Transaktion) siehst eine konsistente Version des Tabellenblatts zu dem Zeitpunkt, als du angefangen hast – auch wenn andere währenddessen schon neue Versionen erzeugt haben.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wie funktioniert MVCC grob intern?
|
||||
|
||||
Vereinfachtes Modell (z.B. ähnlich wie in PostgreSQL/InnoDB):
|
||||
|
||||
### 3.1. Versionen von Zeilen
|
||||
|
||||
Statt einer Zeile gibt es:
|
||||
|
||||
- Version 1: erstellt von Transaktion T1
|
||||
- Version 2: später erstellt von Transaktion T2 (z.B. nach einem UPDATE)
|
||||
- Version 3: später erstellt von T3, usw.
|
||||
|
||||
Jede Version hat Metadaten, z.B.:
|
||||
|
||||
- „ab Transaktion X gültig“
|
||||
- „bis Transaktion Y gültig“
|
||||
(oder „gelöscht ab Transaktion Y“)
|
||||
|
||||
### 3.2. Was sieht eine Transaktion?
|
||||
|
||||
Wenn eine neue Transaktion T liest:
|
||||
|
||||
- Sie merkt sich einen „Zeitpunkt“ (z.B. eine Transaktions-ID oder Timestamp).
|
||||
- Beim Lesen prüft sie pro Zeile:
|
||||
|
||||
- Wurde diese Version **vor** meinem Start erzeugt und war zu dem Zeitpunkt schon committed?
|
||||
- Wurde sie danach erzeugt? Dann darf ich sie nicht sehen.
|
||||
- Ist sie bereits gelöscht oder von einer laufenden Transaktion verändert, die noch nicht committed ist? Dann gilt sie für mich ggf. als nicht existent.
|
||||
|
||||
Das Ergebnis:
|
||||
Du bekommst einen **konsistenten Snapshot** der Daten – so, als ob alle Änderungen nach deinem Start noch nicht existieren.
|
||||
|
||||
### 3.3. Schreiben mit MVCC
|
||||
|
||||
Beim **UPDATE** einer Zeile:
|
||||
|
||||
1. Alte Version bleibt im Speicher (zunächst noch sichtbar für alte Transaktionen).
|
||||
2. Neue Version wird erzeugt, mit „gültig ab Transaktion T“.
|
||||
3. Für neu startende Transaktionen ist **nach Commit** nur noch die neue Version relevant.
|
||||
|
||||
Beim **DELETE**:
|
||||
|
||||
- Die Zeile bekommt sozusagen ein „Todes-Zeitstempel“ (gültig bis T).
|
||||
- Alte Transaktionen sehen sie noch, neue nicht mehr.
|
||||
|
||||
### 3.4. Aufräumen („Vacuum“, Garbage Collection)
|
||||
|
||||
Alte Versionen, die **keine** Transaktion mehr sehen kann, sind Müll:
|
||||
|
||||
- Es gibt Prozess(e), die diese alten Versionen von Zeit zu Zeit **physisch entfernen**.
|
||||
- Beispiel-Begriff: in PostgreSQL „VACUUM“.
|
||||
|
||||
---
|
||||
|
||||
## 4. Abgrenzung zu verwandten Konzepten
|
||||
|
||||
### 4.1. MVCC vs. „klassische“ Sperren (Locking)
|
||||
|
||||
Früher (oder in einfachen Systemen):
|
||||
|
||||
- Jede Transaktion, die liest oder schreibt, setzt **Sperren** (Locks) auf Zeilen oder ganze Tabellen.
|
||||
- Wenn du eine Zeile liest, kann ein strenges System diese Zeile sperren, sodass andere sie nicht ändern dürfen, bis du fertig bist.
|
||||
- Das kann zu Blockaden und Deadlocks führen.
|
||||
|
||||
**MVCC-Ansatz:**
|
||||
|
||||
- Leser blockieren Schreiber **nicht** (oder nur minimal).
|
||||
- Schreiber blockieren Leser **nicht** (solange es nur um das Lesen bereits existierender, „alter“ Versionen geht).
|
||||
- Es gibt zwar weiterhin Sperren, aber sie sind oft nur für Konfliktfälle relevant (z.B. zwei Transaktionen, die **dieselbe** Zeile gleichzeitig ändern wollen).
|
||||
|
||||
MVCC ersetzt also nicht jede Form von Locking, sondern **reduziert** deren Notwendigkeit für Lesezugriffe stark.
|
||||
|
||||
### 4.2. MVCC vs. Snapshot Isolation
|
||||
|
||||
- **MVCC** ist ein **Mechanismus**: mehrere Versionen von Zeilen, Snapshots, Sichtbarkeitsregeln.
|
||||
- **Snapshot Isolation** ist eine **Isolationsebene** (ein bestimmter Garantiesatz für Transaktionen), die **auf MVCC aufbaut**.
|
||||
|
||||
Viele Systeme verwenden MVCC, um Snapshot Isolation umzusetzen:
|
||||
|
||||
- Jede Transaktion sieht den Datenbestand so, wie er beim Start (oder beim Start des Statements) war.
|
||||
- Aber: Snapshot Isolation ist oft **nicht vollständig serialisierbar** (siehe Write Skew Problem weiter unten).
|
||||
|
||||
Kurz:
|
||||
MVCC = Wie die Datenbank Versionen verwaltet.
|
||||
Snapshot Isolation = Welche Sicht und Garantien Transaktionen bekommen.
|
||||
|
||||
### 4.3. MVCC vs. Optimistic Concurrency Control (OCC)
|
||||
|
||||
- **Optimistic Concurrency Control** geht davon aus: Konflikte sind selten, man lässt Transaktionen frei operieren und prüft erst beim Commit, ob es Konflikte gab.
|
||||
- Viele MVCC-Systeme verwenden **Elemente von OCC**: Sie lassen Transaktionen auf einem Snapshot arbeiten und prüfen erst beim Commit, ob sie z.B. eine Zeile verändert haben, die inzwischen jemand anders schon geändert hat.
|
||||
|
||||
MVCC ist also kein direkter Gegensatz zu OCC, sondern eher eine Technik, mit der man OCC effizient implementieren kann.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Probleme werden durch MVCC gelöst?
|
||||
|
||||
### 5.1. Leser blockieren Schreiber nicht (und umgekehrt)
|
||||
|
||||
**Problem ohne MVCC:**
|
||||
Großer Report läuft lange und sperrt viele Zeilen oder Tabellen. Schreibende Transaktionen können nicht mehr weiterarbeiten.
|
||||
|
||||
**Mit MVCC:**
|
||||
|
||||
- Der Report bekommt einen Snapshot.
|
||||
- Während der Report läuft:
|
||||
- Andere Transaktionen können neue Versionen schreiben.
|
||||
- Der Report liest weiterhin die alten Versionen, die für ihn gültig sind.
|
||||
- Ergebnis: lange lesende Transaktionen blockieren das System weit weniger.
|
||||
|
||||
**Praxisbeispiel:**
|
||||
|
||||
- Ein Online-Shop:
|
||||
- Ein Reporting-Job zählt alle Bestellungen des letzten Monats.
|
||||
- Gleichzeitig legen Kunden neue Bestellungen an.
|
||||
- Mit MVCC:
|
||||
- Reporting-Job sieht einen stabilen Stand („Stand 10:00 Uhr“).
|
||||
- Kunden können weiter bestellen; diese Bestellungen erscheinen evtl. nicht im aktuellen Report, aber im nächsten.
|
||||
|
||||
### 5.2. Konsistente Lese-Sichten
|
||||
|
||||
Lesende Transaktionen sehen eine **konsistente Momentaufnahme**, nicht einen Mix aus „halb alt, halb neu“.
|
||||
|
||||
Beispiel:
|
||||
|
||||
- Du willst sicherstellen, dass Summe aller Kontosalden = 0 (Bilanz).
|
||||
- Ohne MVCC könntest du beim Lesen ein Konto schon nach der neuen Buchung sehen, ein anderes aber noch vor der Buchung – Ergebnis: inkonsistente Summe.
|
||||
- Mit MVCC liest du alles so, wie es zu deinem Startzeitpunkt war.
|
||||
|
||||
### 5.3. Hohe Skalierbarkeit für viele Leser
|
||||
|
||||
MVCC ist besonders stark in Systemen mit:
|
||||
|
||||
- hohem Leseanteil (Reports, Dashboards, APIs)
|
||||
- vielen parallelen Nutzern
|
||||
|
||||
Weil Leser nicht permanent Schreiblocks blockieren, kann das System mehr parallele Anfragen verarbeiten.
|
||||
|
||||
### 5.4. Reduzierte Deadlocks
|
||||
|
||||
Deadlocks entstehen oft, wenn zwei Transaktionen sich gegenseitig Sperren wegnehmen wollen.
|
||||
Da MVCC Leser weitgehend von Locks entkoppelt, reduziert sich die Deadlock-Wahrscheinlichkeit, insbesondere bei reinen Lesezugriffen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Welche Herausforderungen und Nachteile hat MVCC?
|
||||
|
||||
### 6.1. Speicher-Overhead durch viele Versionen
|
||||
|
||||
Da alte Versionen zunächst liegen bleiben, kann es passieren:
|
||||
|
||||
- Tabellen werden „aufgebläht“ (Bloat).
|
||||
- Disk-Verbrauch steigt, Indexe werden größer, Performance sinkt.
|
||||
|
||||
Darum braucht man:
|
||||
|
||||
- Mechanismen zur Bereinigung (VACUUM, Garbage Collection)
|
||||
- Tuning (z.B. wie aggressiv wird gereinigt?)
|
||||
|
||||
**Praxisproblem:**
|
||||
Ein System mit dauernd vielen Updates/Deletes, aber schlecht eingestelltem Vacuum, kann plötzlich deutlich langsamer werden, weil die Tabellen intern riesig sind.
|
||||
|
||||
### 6.2. Lang laufende Transaktionen halten alte Versionen fest
|
||||
|
||||
Wenn eine Transaktion sehr lange offen bleibt (z.B. ein Report, der 2 Stunden läuft):
|
||||
|
||||
- Alle Versionen, die **für diese Transaktion sichtbar sein könnten**, müssen erhalten bleiben.
|
||||
- Die Datenbank kann diese Versionen nicht löschen, weil sie noch gebraucht werden.
|
||||
- Folge: Bloat, mehr Speicherverbrauch, mehr I/O, langsamere Zugriffe.
|
||||
|
||||
Praxisnahe Konsequenz:
|
||||
|
||||
- Man versucht in produktiven Systemen:
|
||||
- Lange Transaktionen zu vermeiden oder
|
||||
- Sie auf Replikas/Read-Only-Kopien auszulagern.
|
||||
|
||||
### 6.3. Anomalien unter Snapshot Isolation (Write Skew)
|
||||
|
||||
Snapshot Isolation (häufig via MVCC umgesetzt) ist oft **nicht vollständig serialisierbar**.
|
||||
|
||||
Beispiel (vereinfacht):
|
||||
|
||||
- Zwei Ärzte müssen gleichzeitig im Krankenhaus Dienst haben.
|
||||
- Regel: Es dürfen nie **beide** Ärzte gleichzeitig „Off-Duty“ sein.
|
||||
- Tabelle `dienstplan` mit Einträgen pro Arzt.
|
||||
|
||||
Transaktion T1:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SELECT COUNT(*) FROM dienstplan WHERE on_duty = TRUE;
|
||||
-- Ergebnis: 2 (Arzt A und B sind im Dienst)
|
||||
|
||||
-- T1 setzt Arzt A auf off-duty
|
||||
UPDATE dienstplan SET on_duty = FALSE WHERE arzt = 'A';
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
Transaktion T2 (parallel, mit eigenem Snapshot):
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SELECT COUNT(*) FROM dienstplan WHERE on_duty = TRUE;
|
||||
-- Ergebnis: 2 (sieht noch alten Stand, beide on duty)
|
||||
|
||||
-- T2 setzt Arzt B auf off-duty
|
||||
UPDATE dienstplan SET on_duty = FALSE WHERE arzt = 'B';
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
Beide Transaktionen sehen **denselben alten Stand** („2 Ärzte im Dienst“).
|
||||
Beide halten es für zulässig, je einen Arzt auf off-duty zu setzen.
|
||||
Ergebnis nach beiden Commits: **0 Ärzte im Dienst**, Regel verletzt.
|
||||
|
||||
Das ist ein typischer **Write Skew** – möglich unter Snapshot Isolation, obwohl MVCC sauber arbeitet.
|
||||
|
||||
Lösung:
|
||||
|
||||
- Strengere Isolationsebene: **SERIALIZABLE** (die DB verhindert durch zusätzliche Checks solche Fälle),
|
||||
oder
|
||||
- Explizite Sperren / zusätzliche Constraints durch den Entwickler.
|
||||
|
||||
### 6.4. Komplexere Implementierung und Tuning
|
||||
|
||||
MVCC ist intern relativ komplex:
|
||||
|
||||
- Verwaltung von Versionen
|
||||
- Sichtbarkeitslogik
|
||||
- Garbage Collection
|
||||
- Umgang mit Hot-Spot-Tabellen (sehr häufig aktualisierte Zeilen)
|
||||
|
||||
Für Administratoren/Entwickler bedeutet das:
|
||||
|
||||
- Man muss verstehen, wie die eigene DB MVCC implementiert (PostgreSQL, MySQL InnoDB, Oracle, SQL Server etc. machen es leicht unterschiedlich).
|
||||
- Man muss Parameter fürs Aufräumen und für Isolationsebenen sinnvoll setzen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Praxisnahe Beispiele
|
||||
|
||||
### Beispiel 1: Webshop – Produktpreise ändern
|
||||
|
||||
Situation:
|
||||
|
||||
- Tabelle `produkte` mit Spalte `preis`.
|
||||
- Viele Nutzer sehen gleichzeitig das Produkt im Shop.
|
||||
- Ein Admin ändert den Preis.
|
||||
|
||||
Ohne MVCC (vereinfachte, pessimistische Sperrung):
|
||||
|
||||
- Admin setzt Lock auf Zeile/Produkt.
|
||||
- Solange Admin noch nicht committed hat:
|
||||
- Kunden könnten blockiert werden oder
|
||||
- sie sehen unklare Zwischenzustände.
|
||||
|
||||
Mit MVCC:
|
||||
|
||||
- Admin-Transaktion erzeugt eine **neue Version** der Produktzeile mit neuem Preis.
|
||||
- Kunden, die vor dem Commit lesen:
|
||||
- sehen die **alte Version** (alten Preis).
|
||||
- Kunden, die nach dem Commit lesen:
|
||||
- sehen die **neue Version** (neuen Preis).
|
||||
- Es gibt keine Lese-Blockade während der Preisänderung.
|
||||
|
||||
### Beispiel 2: Reporting vs. Online-Transaktionen
|
||||
|
||||
- Ein Finance-Report soll **Stand Tagesende** alle Buchungen auswerten.
|
||||
- Parallel buchen die Nutzer weiter.
|
||||
|
||||
Mit MVCC:
|
||||
|
||||
- Report bekommt einen Snapshot (z.B. Stand 23:59).
|
||||
- Er arbeitet vielleicht 30 Minuten oder länger.
|
||||
- Währenddessen können Buchungen von 00:00–00:30 weiter eingehen.
|
||||
- Report sieht einen stabilen Zustand; neue Buchungen tauchen erst im nächsten Report auf.
|
||||
|
||||
Das ist ideal für:
|
||||
|
||||
- Data Warehouse Light
|
||||
- tägliche Berichte
|
||||
- Audits (reproduzierbare Sicht)
|
||||
|
||||
### Beispiel 3: Problemfall lange Transaktion
|
||||
|
||||
- Ein Entwickler startet in der Entwicklungsumgebung eine Transaktion:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SELECT * FROM grosse_tabelle; -- dauert lange
|
||||
-- Entwickler vergisst COMMIT oder ROLLBACK
|
||||
```
|
||||
|
||||
- Diese Transaktion bleibt offen.
|
||||
- Alle Versionen, die seit Beginn dieser Transaktion geändert wurden, müssen aufgehoben werden.
|
||||
- Vacuum/Garbage Collector kann viele alte Zeilen nicht löschen.
|
||||
- Nach Stunden/Tagen:
|
||||
- Tabellen aufgebläht,
|
||||
- Performance sinkt,
|
||||
- Admin muss offene Sessions identifizieren und beenden.
|
||||
|
||||
Lernpunkt:
|
||||
Mit MVCC muss man auf **offene, vergessene Transaktionen** achten.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- **MVCC** bedeutet: Zeilen werden nicht einfach überschrieben, sondern es gibt mehrere Versionen derselben Zeile.
|
||||
- Leser und Schreiber können gleichzeitig arbeiten:
|
||||
- Leser sehen einen konsistenten Snapshot,
|
||||
- Schreiber erzeugen neue Versionen, ohne Leser direkt zu blockieren.
|
||||
- MVCC ist der Mechanismus, auf dem Isolationsebenen wie **Snapshot Isolation** aufbauen.
|
||||
- Vorteile:
|
||||
- Hohe Parallelität,
|
||||
- Weniger Sperrkonflikte,
|
||||
- Konsistente Sichten für Reports.
|
||||
- Nachteile/Herausforderungen:
|
||||
- Mehr Speicherbedarf durch alte Versionen,
|
||||
- Notwendigkeit von Aufräumprozessen,
|
||||
- Vorsicht bei langen Transaktionen,
|
||||
- bestimmte Anomalien (z.B. Write Skew) bei Snapshot Isolation.
|
||||
@@ -0,0 +1,360 @@
|
||||
#datenbank
|
||||
|
||||
➡️ [[#Zusammenfassung]]
|
||||
|
||||
---
|
||||
### 1. Grundidee: Was ist eine Materialized View?
|
||||
|
||||
Eine **Materialized View** (materialisierte Sicht) ist vereinfacht gesagt:
|
||||
|
||||
> **Eine gespeicherte Ergebnistabelle einer Abfrage**, die regelmäßig aktualisiert wird.
|
||||
|
||||
Im Unterschied zu einer „normalen“ View, die bei jedem Zugriff die zugrunde liegende Abfrage neu ausführt, werden bei einer Materialized View:
|
||||
|
||||
- die Daten der Abfrage **physisch gespeichert** (wie in einer Tabelle),
|
||||
- und später **wiederverwendet**, ohne jedes Mal die komplette Abfrage neu berechnen zu müssen.
|
||||
|
||||
Du kannst dir das vorstellen wie einen **vorgeberechneten Bericht**, der als echte Tabelle im System liegt, aber technisch aus einer oder mehreren anderen Tabellen abgeleitet ist.
|
||||
|
||||
Typische Verwendung:
|
||||
- Große Tabellen
|
||||
- Aufwendige Joins
|
||||
- Aggregationen (SUM, COUNT, AVG, …)
|
||||
- Reporting & Analytics
|
||||
|
||||
---
|
||||
|
||||
### 2. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
#### 2.1 Normale View vs. Materialized View
|
||||
|
||||
**View (logische Sicht)**:
|
||||
- Ist nur eine **gespeicherte Abfrage**.
|
||||
- Speichert selbst **keine Daten**.
|
||||
- Beim SELECT auf die View wird die zugrunde liegende Abfrage jedes Mal **neu ausgeführt**.
|
||||
- Vorteil: Immer **aktuell**, kein Speicheraufwand.
|
||||
- Nachteil: Kann bei komplexen Abfragen **langsam** sein.
|
||||
|
||||
**Materialized View**:
|
||||
- Ist eine **physisch gespeicherte Tabelle**, die aus einer Abfrage berechnet wurde.
|
||||
- Beim SELECT werden die **vorgehaltenen Daten** gelesen (schnell).
|
||||
- Muss **explizit aktualisiert** („refreshed“) werden.
|
||||
- Vorteil: Schnelle Abfragen, vor allem bei komplexen Berechnungen.
|
||||
- Nachteil: Daten können **veraltet** sein, zusätzlicher Speicher & Wartung.
|
||||
|
||||
#### 2.2 Materialized View vs. Tabelle
|
||||
|
||||
**Normale Tabelle**:
|
||||
- Daten werden direkt in diese Tabelle geschrieben (INSERT, UPDATE, DELETE).
|
||||
- Struktur (Schema) ist unabhängig; die Tabelle „gehört sich selbst“.
|
||||
|
||||
**Materialized View**:
|
||||
- Wird **aus anderen Tabellen abgeleitet** (Definition durch SELECT).
|
||||
- Man schreibt normalerweise **nicht direkt** hinein, sondern nur in die Basistabellen.
|
||||
- Die Materialized View wird über einen **Refresh-Mechanismus** aktualisiert.
|
||||
|
||||
#### 2.3 Materialized View vs. Cache
|
||||
|
||||
Ein **Cache** (z. B. im Application Server):
|
||||
- Hält Daten im Speicher, typischerweise kurzfristig.
|
||||
- Wird meist von der Anwendung gesteuert.
|
||||
- Ist oft flüchtig (z. B. bei Neustart weg).
|
||||
|
||||
**Materialized View**:
|
||||
- Ist Teil der Datenbank, persistent auf Disk.
|
||||
- Wird durch die Datenbank verwaltet.
|
||||
- Kann komplexere Konsistenz- und Refresh-Strategien nutzen.
|
||||
|
||||
#### 2.4 Materialized View vs. Index
|
||||
|
||||
**Index**:
|
||||
- Beschleunigt den Zugriff auf bestehende Daten einer Tabelle.
|
||||
- Speichert typischerweise Schlüsselwerte und Zeiger auf die Tabelle.
|
||||
- Berechnet **keine neuen Inhalte**, sondern hilft, vorhandene schneller zu finden.
|
||||
|
||||
**Materialized View**:
|
||||
- Enthält **eigene Daten**, die Ergebnis einer Abfrage sind (z. B. Summen, Gruppierungen).
|
||||
- Kann zusätzlich selbst Indexe haben.
|
||||
|
||||
#### 2.5 Datenbanken und Begriffe
|
||||
|
||||
Verschiedene Systeme nutzen leicht unterschiedliche Begriffe:
|
||||
|
||||
- **PostgreSQL**: `MATERIALIZED VIEW`
|
||||
- **Oracle**: `MATERIALIZED VIEW` (sehr ausgereifte Funktionen)
|
||||
- **SQL Server**: Kein direkter Begriff, aber **Indexed Views** sind sehr ähnlich
|
||||
- **MySQL**: Keine echte Materialized View, aber man kann das Verhalten nachbauen (z. B. mit Triggern, geplanten Jobs, Tabellen)
|
||||
|
||||
---
|
||||
|
||||
### 3. Welche Probleme werden durch Materialized Views gelöst?
|
||||
|
||||
#### 3.1 Performance bei komplexen Abfragen
|
||||
|
||||
Wenn du häufig dieselbe **komplexe Abfrage** ausführst, kostet das jedes Mal viel Rechenzeit:
|
||||
|
||||
- Viele Joins über große Tabellen
|
||||
- Aggregationen (SUM, COUNT, MAX, …)
|
||||
- Filter auf komplizierten Kombinationen
|
||||
|
||||
Eine Materialized View **berechnet diese Abfrage einmal** (oder in festen Intervallen) und speichert das Ergebnis. Spätere Abfragen greifen nur noch auf die **fertige Ergebnismenge** zu.
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
Du hast einen Online-Shop mit Tabellen:
|
||||
|
||||
- `orders` (Bestellungen)
|
||||
- `order_items` (Bestellpositionen)
|
||||
- `products` (Produkte)
|
||||
- `customers` (Kunden)
|
||||
|
||||
Du willst regelmäßig wissen: **Wie viel Umsatz pro Kunde pro Monat?**
|
||||
|
||||
Ohne Materialized View:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
c.customer_id,
|
||||
date_trunc('month', o.order_date) AS month,
|
||||
SUM(oi.quantity * oi.unit_price) AS revenue
|
||||
FROM customers c
|
||||
JOIN orders o ON o.customer_id = c.customer_id
|
||||
JOIN order_items oi ON oi.order_id = o.order_id
|
||||
GROUP BY c.customer_id, date_trunc('month', o.order_date);
|
||||
```
|
||||
|
||||
Diese Abfrage kann bei Millionen Zeilen **sehr langsam** sein, besonders wenn sie viele Nutzer gleichzeitig ausführen.
|
||||
|
||||
Mit Materialized View:
|
||||
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW mv_customer_monthly_revenue AS
|
||||
SELECT
|
||||
c.customer_id,
|
||||
date_trunc('month', o.order_date) AS month,
|
||||
SUM(oi.quantity * oi.unit_price) AS revenue
|
||||
FROM customers c
|
||||
JOIN orders o ON o.customer_id = c.customer_id
|
||||
JOIN order_items oi ON oi.order_id = o.order_id
|
||||
GROUP BY c.customer_id, date_trunc('month', o.order_date);
|
||||
```
|
||||
|
||||
Einfache Abfrage darauf:
|
||||
|
||||
```sql
|
||||
SELECT * FROM mv_customer_monthly_revenue
|
||||
WHERE month = '2025-01-01';
|
||||
```
|
||||
|
||||
Das ist meist **sehr schnell**, da nur noch fertige Zeilen gelesen werden.
|
||||
|
||||
#### 3.2 Entlastung der Primärtables
|
||||
|
||||
Wenn viele Nutzer komplexe Analysen auf den operativen Tabellen ausführen, kann das:
|
||||
|
||||
- den Datenbankserver stark belasten,
|
||||
- Transaktionen verlangsamen,
|
||||
- das Online-System (z. B. Shop) spürbar ausbremsen.
|
||||
|
||||
Materialized Views dienen hier als eine Art **vorgefertigtes Reporting-Layer**, das:
|
||||
|
||||
- weniger Schreiboperationen hat,
|
||||
- stark komprimiert / aggregiert sein kann,
|
||||
- unabhängig indexiert werden kann.
|
||||
|
||||
#### 3.3 Zugriff auf entfernte Daten (z. B. Data Warehouse)
|
||||
|
||||
In manchen Systemen kann eine Materialized View Daten aus **anderen Datenbanken** oder **externen Quellen** einbinden. Damit können z. B.:
|
||||
|
||||
- Daten aus mehreren Systemen
|
||||
- in einer lokal gespeicherten, performanten Sicht
|
||||
zusammengefasst werden.
|
||||
|
||||
---
|
||||
|
||||
### 4. Welche Herausforderungen und Nachteile gibt es?
|
||||
|
||||
#### 4.1 Datenaktualität (Freshness)
|
||||
|
||||
Materialized Views sind **nicht automatisch immer aktuell**.
|
||||
|
||||
Typische Arten des Refresh:
|
||||
|
||||
1. **Maneller Refresh**
|
||||
Du rufst selbst etwas auf wie:
|
||||
|
||||
```sql
|
||||
REFRESH MATERIALIZED VIEW mv_customer_monthly_revenue;
|
||||
```
|
||||
|
||||
2. **Geplanter Refresh (z. B. jede Stunde/Tageswechsel)**
|
||||
Über Scheduler/Jobs: „Führe jede Nacht um 3 Uhr einen Refresh aus“.
|
||||
|
||||
3. **On Commit / nahezu in Echtzeit** (je nach DB)
|
||||
Bei bestimmten Systemen können Materialized Views nach Änderungen an den Basistabellen automatisch aktualisiert werden.
|
||||
|
||||
**Konsequenz:**
|
||||
Es gibt immer einen Trade-off zwischen:
|
||||
|
||||
- **Aktualität** (häufig refresht → näher an „Echtzeit“)
|
||||
- **Performance/Belastung** (jeder Refresh ist teils teuer)
|
||||
|
||||
#### 4.2 Konsistenz und Komplexität
|
||||
|
||||
Bei vielen Materialized Views, die sich überlappen oder von anderen Views abhängen, kann es kompliziert werden:
|
||||
|
||||
- In welcher Reihenfolge refresht man?
|
||||
- Was passiert, wenn eine Quelle fehlerhaft ist?
|
||||
- Wie geht man mit teilweisen Refreshes um?
|
||||
|
||||
#### 4.3 Speicherbedarf
|
||||
|
||||
Materialized Views brauchen **zusätzlichen Speicher**, da sie Daten duplizieren:
|
||||
|
||||
- Daten sind in den Basistabellen vorhanden
|
||||
- plus noch einmal in den Materialized Views
|
||||
|
||||
Je nach Anzahl und Detailgrad kann das signifikant sein.
|
||||
|
||||
#### 4.4 Schreibaufwand auf Basistabellen (indirekt)
|
||||
|
||||
Wenn Materialized Views sehr häufig aktualisiert werden:
|
||||
|
||||
- können Inserts/Updates auf Basistabellen indirekt langsamer werden,
|
||||
- weil der Refresh-Prozess Ressourcen frisst (z. B. CPU, I/O, Locks).
|
||||
|
||||
Bei Systemen mit „Refresh on Commit“ müssen evtl. zusätzliche Metadaten gepflegt werden, um Änderungen nachvollziehbar zu machen.
|
||||
|
||||
#### 4.5 Komplexität im Design und Betrieb
|
||||
|
||||
- Man muss Entscheidungen treffen:
|
||||
- Welche Abfragen lohnen sich als Materialized View?
|
||||
- Wie oft sollten sie aktualisiert werden?
|
||||
- Wer ist verantwortlich für Monitoring und Fehlerbehandlung?
|
||||
|
||||
- Bei Änderungen am Schema (z. B. Spalten hinzufügen) muss oft:
|
||||
- die Materialized View angepasst
|
||||
- oder neu aufgebaut werden.
|
||||
|
||||
---
|
||||
|
||||
### 5. Praxisnahe Beispiele
|
||||
|
||||
#### 5.1 Reporting im E-Commerce
|
||||
|
||||
Stell dir einen Online-Shop vor, der täglich Tausende Bestellungen hat. Das Management möchte im Dashboard sehen:
|
||||
|
||||
- Umsatz pro Tag
|
||||
- Top-10-Produkte pro Woche
|
||||
- Anzahl neuer Kunden pro Monat
|
||||
|
||||
Ohne Materialized Views würden diese Reports bei jedem Aufruf heavy Queries auf großen Tabellen ausführen.
|
||||
|
||||
Mit Materialized Views:
|
||||
|
||||
1. `mv_daily_revenue`
|
||||
Umsätze pro Tag
|
||||
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW mv_daily_revenue AS
|
||||
SELECT
|
||||
date_trunc('day', o.order_date) AS day,
|
||||
SUM(oi.quantity * oi.unit_price) AS revenue
|
||||
FROM orders o
|
||||
JOIN order_items oi ON oi.order_id = o.order_id
|
||||
GROUP BY date_trunc('day', o.order_date);
|
||||
```
|
||||
|
||||
2. `mv_weekly_top_products`
|
||||
Top-Produkte je Woche
|
||||
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW mv_weekly_top_products AS
|
||||
SELECT
|
||||
date_trunc('week', o.order_date) AS week,
|
||||
oi.product_id,
|
||||
SUM(oi.quantity) AS total_quantity
|
||||
FROM orders o
|
||||
JOIN order_items oi ON oi.order_id = o.order_id
|
||||
GROUP BY date_trunc('week', o.order_date), oi.product_id;
|
||||
```
|
||||
|
||||
3. `mv_monthly_new_customers`
|
||||
Neue Kunden pro Monat
|
||||
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW mv_monthly_new_customers AS
|
||||
SELECT
|
||||
date_trunc('month', c.created_at) AS month,
|
||||
COUNT(*) AS new_customers
|
||||
FROM customers c
|
||||
GROUP BY date_trunc('month', c.created_at);
|
||||
```
|
||||
|
||||
Diese Views kannst du z. B. **jede Nacht** aktualisieren, da sich historische Daten nicht mehr ändern.
|
||||
|
||||
#### 5.2 Data Warehouse / BI
|
||||
|
||||
In einem Data Warehouse gibt es oft:
|
||||
|
||||
- große „Faktentabellen“ (z. B. `fact_sales` mit hunderten Millionen Zeilen),
|
||||
- Dimensionstabellen (Kunde, Produkt, Region).
|
||||
|
||||
Materialized Views können hier als **„Summary Tables“** dienen, z. B.:
|
||||
|
||||
- Verkäufe pro Region und Monat
|
||||
- Verkäufe pro Produktkategorie und Quartal
|
||||
|
||||
Analysten müssen dann nicht mehr auf die komplette Faktentabelle zugreifen, sondern nur noch auf relativ kleine, aggregierte Materialized Views.
|
||||
|
||||
#### 5.3 Teilweise Aktualisierung (Incremental Refresh)
|
||||
|
||||
Fortgeschrittene Systeme (z. B. Oracle) können Materialized Views **inkrementell** aktualisieren: Es werden nur die Änderungen seit dem letzten Stand eingerechnet, statt alles komplett neu zu berechnen. Das spart enorm Zeit und Ressourcen, ist aber vom Setup her komplexer.
|
||||
|
||||
---
|
||||
|
||||
### 6. Typische Strategien und Best Practices
|
||||
|
||||
1. **Nur für „schwere“ Abfragen nutzen**
|
||||
Materialized Views lohnen sich insbesondere, wenn:
|
||||
- die Abfrage sehr teuer ist,
|
||||
- die Daten sich nicht „jede Sekunde“ ändern,
|
||||
- die Ergebnisse häufig angefragt werden.
|
||||
|
||||
2. **Refresh-Frequenz bewusst wählen**
|
||||
- Nahe Echtzeit notwendig? → Häufigerer Refresh, mehr Last
|
||||
- Reine Reports (z. B. täglich) → nächtlicher Refresh reicht oft
|
||||
|
||||
3. **Materialized Views benennen und dokumentieren**
|
||||
- Sinnvolle Namen (`mv_...`)
|
||||
- Dokumentation: Welche Abfrage? Wie wird refresht? Wer nutzt sie?
|
||||
|
||||
4. **Indexe auf Materialized Views setzen**
|
||||
- Genau wie bei normalen Tabellen können Indexe Abfragen weiter beschleunigen:
|
||||
```sql
|
||||
CREATE INDEX idx_mv_daily_revenue_day
|
||||
ON mv_daily_revenue (day);
|
||||
```
|
||||
|
||||
5. **Monitoring**
|
||||
- Refresh-Zeiten messen
|
||||
- Fehler beim Refresh protokollieren
|
||||
- Speicherverbrauch im Blick behalten
|
||||
|
||||
---
|
||||
|
||||
### Zusammenfassung
|
||||
|
||||
- **Materialized Views** sind gespeicherte (materialisierte) Ergebnisse von Abfragen, die wie Tabellen genutzt werden können.
|
||||
- Sie dienen vor allem zur **Performance-Steigerung** bei komplexen oder häufig ausgeführten Abfragen und zur **Entlastung** der operativen Tabellen.
|
||||
- Gegenüber normalen Views:
|
||||
- **schneller** beim Lesen,
|
||||
- aber **nicht automatisch aktuell** – sie müssen refresht werden.
|
||||
- Herausforderungen:
|
||||
- Datenaktualität (wie oft refresht man?),
|
||||
- zusätzlicher Speicherverbrauch,
|
||||
- Komplexität in Design und Wartung.
|
||||
- Typische Einsatzgebiete:
|
||||
- Reporting, Analytics, Dashboards,
|
||||
- Data Warehousing,
|
||||
- Aggregationen über große Datenmengen.
|
||||
@@ -0,0 +1,411 @@
|
||||
Hier eine Einführung in „asynchron“ in der Python-Programmierung, für jemanden ohne Vorkenntnisse in diesem Bereich.
|
||||
|
||||
---
|
||||
|
||||
## 1. Intuitive Vorstellung: Was heißt „asynchron“?
|
||||
|
||||
Stell dir vor, du kochst:
|
||||
|
||||
- Du stellst Wasser auf den Herd (es braucht Zeit, bis es kocht).
|
||||
- Während du wartest, schneidest du Gemüse, bereitest Sauce vor usw.
|
||||
- Du **blockierst nicht** deine Zeit, indem du nur auf den Topf starrst.
|
||||
|
||||
**Asynchron** bedeutet in der Programmierung:
|
||||
Dein Programm kann etwas starten, das länger dauert (z.B. eine Netzwerkabfrage), und während es darauf wartet, **andere Dinge erledigen**, statt „untätig“ zu blockieren.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grundbegriffe: synchron vs. asynchron
|
||||
|
||||
### 2.1 Synchron (blockierend)
|
||||
|
||||
Synchroner, „klassischer“ Code:
|
||||
|
||||
- Befehl A wird ausgeführt.
|
||||
- Erst wenn A fertig ist, wird B ausgeführt.
|
||||
- Wenn A lange wartet (z.B. auf eine Antwort aus dem Internet), **steht das ganze Programm an dieser Stelle still**.
|
||||
|
||||
Beispiel (synchron, blockierend):
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
print("Starte")
|
||||
time.sleep(5) # wartet 5 Sekunden – Programm ist blockiert
|
||||
print("Fertig")
|
||||
```
|
||||
|
||||
Während `time.sleep(5)` läuft, kann das Programm nichts anderes tun.
|
||||
|
||||
### 2.2 Asynchron (nicht-blockierend innerhalb eines Ablaufs)
|
||||
|
||||
Asynchroner Code versucht:
|
||||
|
||||
- Langsame Operationen (z.B. Netzwerk, Festplatte, Datenbank) so zu starten,
|
||||
- und während sie „laufen“, andere Aufgaben zu bearbeiten.
|
||||
|
||||
In Python machst du das typischerweise mit `async` und `await`.
|
||||
|
||||
Sehr vereinfachtes Beispiel:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def aufgabe(name, dauer):
|
||||
print(f"{name} gestartet")
|
||||
await asyncio.sleep(dauer) # nicht-blockierend warten
|
||||
print(f"{name} fertig")
|
||||
|
||||
async def main():
|
||||
# Zwei Aufgaben (Tasks) gleichzeitig laufen lassen
|
||||
task1 = asyncio.create_task(aufgabe("A", 2))
|
||||
task2 = asyncio.create_task(aufgabe("B", 2))
|
||||
|
||||
await task1
|
||||
await task2
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Typischer Ablauf:
|
||||
- „A gestartet“
|
||||
- „B gestartet“
|
||||
- (2 Sekunden vergehen)
|
||||
- „A fertig“
|
||||
- „B fertig“
|
||||
|
||||
Beide Aufgaben „warten gleichzeitig“, und die Zeit überlappt sich.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wichtige Abgrenzungen: Begriffe, die oft durcheinandergehen
|
||||
|
||||
### 3.1 Nebenläufigkeit (Concurrency) vs. Parallelität
|
||||
|
||||
- **Nebenläufigkeit (Concurrency)**: Mehrere Aufgaben werden so organisiert, dass sie *scheinbar gleichzeitig* laufen, indem man schnell zwischen ihnen hin- und herschaltet.
|
||||
- **Parallelität (Parallelism)**: Mehrere Aufgaben laufen *wirklich gleichzeitig* auf mehreren CPU-Kernen.
|
||||
|
||||
Asynchrones Programmieren in Python (`asyncio`) ist in erster Linie ein Werkzeug für **Nebenläufigkeit**, nicht zwingend für echte Parallelität.
|
||||
|
||||
### 3.2 Threads vs. Async
|
||||
|
||||
- **Threads**:
|
||||
- Betriebssystem-Fäden (OS-Threads).
|
||||
- Können an verschiedenen CPU-Kernen parallel laufen.
|
||||
- Schwerer zu testen, zu debuggen (Race Conditions, Deadlocks).
|
||||
- **Async (z.B. asyncio in Python)**:
|
||||
- Läuft typischerweise in **einem** Thread.
|
||||
- Nutzt einen **Event Loop**, um zwischen Aufgaben zu wechseln, wenn sie gerade warten.
|
||||
- Sehr gut geeignet, wenn viele Aufgaben hauptsächlich **I/O-lastig** sind (Netzwerk, Dateien).
|
||||
|
||||
**Kurz:**
|
||||
- Viele Netzwerk-Anfragen gleichzeitig? → Async kann ideal sein.
|
||||
- Viel CPU-Berechnung (z.B. Bildverarbeitung)? → Threads oder Prozesse (Multiprocessing) sind oft sinnvoller.
|
||||
|
||||
### 3.3 Blocking vs. Non-blocking I/O
|
||||
|
||||
- **Blockierend**: „Lies aus dem Netzwerk“ – der Code bleibt stehen, bis Daten da sind.
|
||||
- **Non-blockierend**: „Lies aus dem Netzwerk, aber wenn gerade nichts da ist, mach solange andere Aufgaben.“
|
||||
|
||||
Asynchrones Python nutzt non-blocking I/O und einen Event Loop, um viele solcher Operationen gleichzeitig zu verwalten.
|
||||
|
||||
---
|
||||
|
||||
## 4. Asynchron in Python konkret: `asyncio`, `async`, `await`
|
||||
|
||||
### 4.1 Historischer Kontext
|
||||
|
||||
- Vor Python 3.4 gab es `asyncio` nur als externes Paket.
|
||||
- Ab Python 3.5 wurden die Schlüsselwörter `async` und `await` eingeführt und haben das Arbeiten mit Async deutlich angenehmer gemacht.
|
||||
|
||||
### 4.2 Zentrale Begriffe
|
||||
|
||||
- **Coroutine**: eine Funktion, die „angehalten“ und später fortgesetzt werden kann. In Python: definiert mit `async def`.
|
||||
- **Event Loop**: eine Schleife, die:
|
||||
- Aufgaben plant,
|
||||
- sie laufen lässt, bis sie warten müssen (z.B. auf I/O),
|
||||
- dann anderen Aufgaben CPU-Zeit gibt.
|
||||
- **Task**: eine geplante Coroutine, die vom Event Loop verwaltet wird.
|
||||
- **Future**: ein Platzhalter für ein Ergebnis, das noch nicht fertig ist.
|
||||
|
||||
### 4.3 Einfaches Beispiel: Event Loop und Coroutines
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def hallo():
|
||||
print("Hallo...")
|
||||
await asyncio.sleep(1) # simuliert I/O-Wartezeit
|
||||
print("...Welt!")
|
||||
|
||||
async def main():
|
||||
await hallo()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
- `hallo()` ist eine Coroutine.
|
||||
- `await asyncio.sleep(1)` bedeutet: „warte 1 Sekunde, aber blockiere nicht den Event Loop“.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Probleme werden durch asynchrones Programmieren gelöst?
|
||||
|
||||
### 5.1 Viele gleichzeitige I/O-Aufgaben
|
||||
|
||||
Typische Beispiele:
|
||||
|
||||
- Webserver, die viele gleichzeitige HTTP-Anfragen beantworten.
|
||||
- Web-Scraper oder Clients, die viele HTTP-Anfragen an andere Server stellen.
|
||||
- Chat-Server, WebSockets, Streaming.
|
||||
- Programme, die gleichzeitig:
|
||||
- Dateien lesen/schreiben,
|
||||
- mit einer Datenbank kommunizieren,
|
||||
- HTTP-Anfragen senden.
|
||||
|
||||
**Synchroner Ansatz**:
|
||||
Jede Anfrage blockiert einen Thread/Prozess, solange sie auf Antwort wartet → sehr viele Threads/Prozesse nötig.
|
||||
|
||||
**Asynchroner Ansatz**:
|
||||
Ein Event Loop verwaltet viele tausend Verbindungen in einem oder wenigen Threads, indem er immer dort weiterarbeitet, wo gerade Daten verfügbar sind.
|
||||
|
||||
### 5.2 Bessere Ressourcennutzung bei I/O-lastigen Programmen
|
||||
|
||||
Wenn dein Programm hauptsächlich:
|
||||
|
||||
- Daten lädt (HTTP, DB),
|
||||
- auf Antworten wartet,
|
||||
- nicht viel rechnet,
|
||||
|
||||
dann ist asynchroner Code oft **effizienter** (weniger Overhead, weniger Threads, bessere Skalierung).
|
||||
|
||||
### 5.3 Responsivere Anwendungen (z.B. GUIs)
|
||||
|
||||
In grafischen Anwendungen (oder auch CLI-Tools) willst du:
|
||||
|
||||
- Nicht, dass die Oberfläche „einfriert“, während eine Anfrage ans Internet läuft.
|
||||
- Stattdessen nutzt du asynchrone oder nebenläufige Mechanismen, damit der Haupt-Thread weiterhin Eingaben entgegennimmt.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele
|
||||
|
||||
### 6.1 Vergleich: synchron vs. asynchron HTTP-Anfragen
|
||||
|
||||
#### Synchron: nacheinander mit `requests`
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
urls = [
|
||||
"https://example.com",
|
||||
"https://httpbin.org/delay/2",
|
||||
"https://httpbin.org/delay/3",
|
||||
]
|
||||
|
||||
def fetch(url):
|
||||
print(f"Rufe {url} ab...")
|
||||
response = requests.get(url)
|
||||
print(f"{url}: Status {response.status_code}")
|
||||
|
||||
def main():
|
||||
for url in urls:
|
||||
fetch(url)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
- Jede Anfrage wartet, bis sie fertig ist.
|
||||
- Gesamtzeit ≈ Summe aller Wartezeiten.
|
||||
|
||||
#### Asynchron: gleichzeitig mit `aiohttp` und `asyncio`
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
urls = [
|
||||
"https://example.com",
|
||||
"https://httpbin.org/delay/2",
|
||||
"https://httpbin.org/delay/3",
|
||||
]
|
||||
|
||||
async def fetch(session, url):
|
||||
print(f"Rufe {url} ab...")
|
||||
async with session.get(url) as response:
|
||||
print(f"{url}: Status {response.status}")
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
tasks = [fetch(session, url) for url in urls]
|
||||
await asyncio.gather(*tasks) # starte alle gleichzeitig
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
- Alle Anfragen werden „gleichzeitig“ gestartet.
|
||||
- Gesamtzeit ≈ maximale Einzeldauer, nicht Summe.
|
||||
|
||||
### 6.2 Viele „Schlaf-Aufgaben“ parallel (Simulation von I/O)
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
async def simulierte_io_aufgabe(n):
|
||||
dauer = random.uniform(0.5, 2.0)
|
||||
print(f"Aufgabe {n} startet, Dauer ~{dauer:.2f}s")
|
||||
await asyncio.sleep(dauer)
|
||||
print(f"Aufgabe {n} fertig")
|
||||
|
||||
async def main():
|
||||
tasks = [simulierte_io_aufgabe(i) for i in range(5)]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Output (ähnlich):
|
||||
|
||||
- Mehrere Aufgaben starten schnell hintereinander.
|
||||
- Sie enden in anderer Reihenfolge, je nach Dauer.
|
||||
- Die Gesamtzeit liegt in etwa bei der **längsten** Wartezeit, nicht bei der Summe aller.
|
||||
|
||||
---
|
||||
|
||||
## 7. Typische Herausforderungen und Stolperfallen
|
||||
|
||||
### 7.1 Denken in „async“ ist ungewohnt
|
||||
|
||||
Für Einsteiger:
|
||||
- Man kann `await` **nur** in `async def`-Funktionen verwenden.
|
||||
- Asynchrone Funktionen verhalten sich anders als normale:
|
||||
|
||||
```python
|
||||
async def foo():
|
||||
return 42
|
||||
|
||||
# Aufruf:
|
||||
result = foo() # das ist KEINE 42, sondern eine Coroutine!
|
||||
```
|
||||
|
||||
Du musst sie ausführen:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def foo():
|
||||
return 42
|
||||
|
||||
async def main():
|
||||
result = await foo()
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 7.2 Blockierender Code in asynchronem Kontext
|
||||
|
||||
Problem:
|
||||
- Du hast eine `async`-Funktion, benutzt darin aber eine **blockierende** Bibliothek (z.B. `requests`, `time.sleep`).
|
||||
- Dann blockierst du trotzdem den Event Loop, obwohl du „async“ verwendest.
|
||||
|
||||
Beispiel (so besser nicht):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
async def schlecht():
|
||||
print("Blockiere Event Loop...")
|
||||
time.sleep(5) # blockiert den Event Loop komplett!
|
||||
print("Weiter geht's")
|
||||
|
||||
asyncio.run(schlecht())
|
||||
```
|
||||
|
||||
Lösung:
|
||||
- Entweder eine **asynchrone Alternative** verwenden (z.B. `aiohttp` statt `requests`).
|
||||
- Oder blockierende Funktion in einem Thread/Prozess auslagern (z.B. `asyncio.to_thread`).
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
def blockierende_funktion():
|
||||
time.sleep(5)
|
||||
return "fertig"
|
||||
|
||||
async def gut():
|
||||
print("Starte blockierende Funktion in Thread...")
|
||||
result = await asyncio.to_thread(blockierende_funktion)
|
||||
print("Ergebnis:", result)
|
||||
|
||||
asyncio.run(gut())
|
||||
```
|
||||
|
||||
### 7.3 Debugging und Fehlersuche
|
||||
|
||||
- Fehler in asynchronen Programmen können schwerer nachzuvollziehen sein.
|
||||
- Stack-Traces sehen anders aus, weil Coroutines, Tasks und der Event Loop beteiligt sind.
|
||||
- Es kann passieren, dass Tasks „stillschweigend“ fehlschlagen, wenn man sie nicht korrekt awaited oder Fehler nicht abfängt.
|
||||
|
||||
### 7.4 Testen von asynchronem Code
|
||||
|
||||
- Unit-Tests brauchen meist auch einen Event Loop.
|
||||
- Viele Testframeworks bieten dafür Mechanismen (`pytest` mit `pytest-asyncio`).
|
||||
|
||||
Beispiel mit `pytest-asyncio`:
|
||||
|
||||
```python
|
||||
# test_example.py
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
async def verdoppeln(x):
|
||||
await asyncio.sleep(0.1)
|
||||
return x * 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_verdoppeln():
|
||||
assert await verdoppeln(21) == 42
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Wann lohnt sich asynchrones Programmieren (und wann nicht)?
|
||||
|
||||
**Sinnvoll bei:**
|
||||
|
||||
- Web-APIs, Microservices, Webserver.
|
||||
- Chat-Server, WebSocket-Anwendungen.
|
||||
- Web-Scraping vieler Seiten gleichzeitig.
|
||||
- I/O-lastigen Programmen mit vielen Netzwerk- oder Datenbankzugriffen.
|
||||
|
||||
**Weniger sinnvoll bei:**
|
||||
|
||||
- Reinen CPU-lastigen Aufgaben (z.B. numerische Berechnungen, Bildverarbeitung).
|
||||
- Hier helfen eher: mehrere Prozesse (`multiprocessing`) oder spezialisierte Bibliotheken (NumPy, Numba, etc.).
|
||||
- Kleinen Scripts, die nur wenige, einfache Schritte nacheinander machen – da ist synchroner Code oft einfacher und ausreichend.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zusammenfassung in einfachen Worten
|
||||
|
||||
- **Asynchron** in Python bedeutet:
|
||||
Du kannst zeitaufwändige, I/O-lastige Aufgaben starten und in der Zwischenzeit andere Aufgaben erledigen, statt auf jede einzelne zu warten.
|
||||
|
||||
- Die Mechanismen dafür sind:
|
||||
- `async def` (Coroutines),
|
||||
- `await` (warten, ohne zu blockieren),
|
||||
- ein **Event Loop** (z.B. in `asyncio`).
|
||||
|
||||
- Es löst besonders gut Probleme mit **vielen gleichzeitigen I/O-Operationen** (Netzwerk, Datenbanken), wie bei Webservern und Web-Scrapern.
|
||||
|
||||
- Herausforderungen:
|
||||
- Umdenken gegenüber normalem, synchronem Code.
|
||||
- Aufpassen, keine blockierenden Funktionen im Event Loop zu verwenden.
|
||||
- Debugging und Testen sind etwas komplexer.
|
||||
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
Im Folgenden bekommst du eine umfassende, aber einsteigerfreundliche Einführung in FastAPI.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee: Was ist FastAPI?
|
||||
|
||||
**FastAPI** ist ein modernes, schnelles Web-Framework für Python, mit dem du **Web-APIs** (Schnittstellen) bauen kannst.
|
||||
Eine API ist eine „Schnittstelle“, über die andere Programme mit deinem Programm sprechen können – z. B. eine Web-App, ein Mobile-App-Backend oder interne Services in einem Unternehmen.
|
||||
|
||||
Kernpunkte von FastAPI:
|
||||
|
||||
- **Schwerpunkt:** Aufbau von **HTTP-APIs** (REST-APIs, JSON-basierte APIs).
|
||||
- **Geschwindigkeit:** Sehr performant durch Nutzung von **asynchronem Python** (`async`/`await`), basierend auf **ASGI**.
|
||||
- **Typisierung:** Starke Nutzung von **Python-Typannotationen** (z. B. `str`, `int`, eigene Klassen).
|
||||
→ Daraus entstehen automatisch:
|
||||
- Validierung von Daten,
|
||||
- automatische Dokumentation (Swagger / OpenAPI),
|
||||
- bessere IDE-Unterstützung (Autovervollständigung, Fehlererkennung).
|
||||
- **Auto-Dokumentation:** FastAPI generiert automatisch eine **interaktive API-Dokumentation** im Browser.
|
||||
|
||||
Ein typisches „Hello World“ mit FastAPI sieht so aus:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
Starten kannst du das z. B. mit:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
Dann ist die API z. B. unter `http://127.0.0.1:8000` erreichbar.
|
||||
|
||||
---
|
||||
|
||||
## 2. Abgrenzung: FastAPI vs. verwandte Begriffe und Frameworks
|
||||
|
||||
### 2.1 FastAPI vs. „API“ / REST / HTTP allgemein
|
||||
|
||||
- **HTTP**: Das zugrunde liegende Protokoll, über das Browser oder andere Dienste kommunizieren.
|
||||
- **REST-API**: Eine Art, HTTP-APIs zu strukturieren (z. B. `GET /users`, `POST /orders`).
|
||||
- **FastAPI**: Ein **Framework**, das dir hilft, solche HTTP/REST-APIs in Python zu bauen.
|
||||
|
||||
FastAPI „spricht“ also HTTP, baut REST-APIs, ist aber selbst das **Werkzeug**, kein Protokoll.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 FastAPI vs. Flask
|
||||
|
||||
**Flask** ist ein sehr bekanntes, minimalistisches Python-Webframework.
|
||||
|
||||
**Ähnlichkeiten:**
|
||||
|
||||
- Beide erlauben es, mit wenig Code HTTP-Endpunkte zu definieren.
|
||||
- Beide sind relativ leichtgewichtig und flexibel.
|
||||
|
||||
**Unterschiede:**
|
||||
|
||||
- **Asynchronität**:
|
||||
- Flask: traditionell synchron (WSGI), Async ist erst neuerdings und eingeschränkt verfügbar.
|
||||
- FastAPI: von Anfang an für **async** gebaut (ASGI).
|
||||
- **Typen & Validierung**:
|
||||
- Flask: Kein eingebautes System für automatische Validierung – du machst das selbst oder mit Erweiterungen.
|
||||
- FastAPI: Nutzt **[[Pydantic]]**-Modelle und Typannotationen → automatische Validierung.
|
||||
- **Dokumentation**:
|
||||
- Flask: Kein automatisches API-Dokumentations-UI.
|
||||
- FastAPI: Automatisch generierte OpenAPI/Swagger-UI unter `/docs` und `/redoc`.
|
||||
|
||||
Praxisbeispiel Vergleich:
|
||||
|
||||
**Flask:**
|
||||
|
||||
```python
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/items", methods=["POST"])
|
||||
def create_item():
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
price = data.get("price")
|
||||
if not isinstance(name, str) or not isinstance(price, (int, float)):
|
||||
return jsonify({"error": "Invalid data"}), 400
|
||||
return jsonify({"name": name, "price": price})
|
||||
```
|
||||
|
||||
**FastAPI:**
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
|
||||
@app.post("/items")
|
||||
def create_item(item: Item):
|
||||
# item ist schon validiert
|
||||
return item
|
||||
```
|
||||
|
||||
FastAPI übernimmt hier die Validierung automatisch.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 FastAPI vs. Django (und Django REST Framework)
|
||||
|
||||
**Django** ist ein „Full-Stack“-Framework:
|
||||
|
||||
- liefert Templates, ORM (Datenbankzugriff), Admin-Interface, Auth-System, Formulare etc.
|
||||
- ideal für klassische Webanwendungen mit HTML-Seiten.
|
||||
|
||||
Für APIs nutzt man meist **Django REST Framework (DRF)** als Erweiterung.
|
||||
|
||||
**FastAPI** dagegen ist:
|
||||
|
||||
- stärker auf **APIs** fokussiert,
|
||||
- nicht „alles aus einer Hand“, sondern:
|
||||
- Web-Layer: `Starlette`,
|
||||
- Datenvalidierung: `Pydantic`,
|
||||
- Datenbank: du wählst selbst z. B. SQLAlchemy, Tortoise ORM etc.
|
||||
|
||||
Faustregel:
|
||||
|
||||
- Wenn du eine klassische Website mit HTML-Rendering brauchst → Django.
|
||||
- Wenn du primär eine performant API bauen willst (z. B. für SPA, Microservices) → FastAPI ist sehr attraktiv.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 FastAPI vs. Node.js / Express
|
||||
|
||||
**Node.js + Express** ist eine sehr verbreitete Lösung für APIs in JavaScript/TypeScript.
|
||||
|
||||
- **Sprache:** Node → JavaScript/TypeScript, FastAPI → Python.
|
||||
- **Typen:** TypeScript kann Typen bieten, FastAPI nutzt Python-Typen + [[Pydantic]].
|
||||
- **Ökosystem:** Node sehr stark im Web-/Frontend-nahen Bereich, Python stark bei Data Science, Machine Learning und Backend-Services.
|
||||
|
||||
FastAPI ist besonders interessant, wenn du sowieso Python nutzt (z. B. wegen ML/AI) und dafür eine passende Web-API brauchst.
|
||||
|
||||
---
|
||||
|
||||
## 3. Welche Probleme löst FastAPI?
|
||||
|
||||
### 3.1 Saubere, valide Eingabedaten
|
||||
|
||||
Problem ohne Framework-Unterstützung:
|
||||
|
||||
- Du bekommst z. B. einen JSON-Body und musst:
|
||||
- alle Felder prüfen (Typ, Pflichtfelder, Wertebereiche),
|
||||
- Fehler verständlich zurückgeben,
|
||||
- alles manuell machen.
|
||||
|
||||
FastAPI + [[Pydantic]] lösen das:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class User(BaseModel):
|
||||
name: str = Field(..., min_length=3)
|
||||
age: int = Field(..., ge=0, le=120) # 0 <= age <= 120
|
||||
|
||||
@app.post("/users")
|
||||
def create_user(user: User):
|
||||
# Wenn name zu kurz oder age negativ ist, liefert FastAPI automatisch 422 mit Fehlerdetails
|
||||
return {"message": "User created", "user": user}
|
||||
```
|
||||
|
||||
Vorteil:
|
||||
|
||||
- Weniger Fehleranfälligkeit.
|
||||
- Konsistente Fehlerantworten.
|
||||
- Gute Developer-Erfahrung.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Automatische Dokumentation und Testbarkeit
|
||||
|
||||
FastAPI erzeugt automatisch eine OpenAPI-Spezifikation und UI:
|
||||
|
||||
- `http://localhost:8000/docs` → Swagger UI (interaktive Oberfläche, du kannst Requests direkt aus dem Browser abschicken).
|
||||
- `http://localhost:8000/redoc` → ReDoc, alternative Dokumentationsansicht.
|
||||
|
||||
Das hilft:
|
||||
|
||||
- Dir selbst beim Testen.
|
||||
- Frontend-Entwicklern oder anderen Teams, die deine API nutzen.
|
||||
- Beim automatisierten Generieren von Client-SDKs (z. B. TypeScript-Client).
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Performance und asynchrones I/O
|
||||
|
||||
Problem:
|
||||
|
||||
- In „klassischen“ synchronen Webframeworks blockiert jeder Request, der z. B. auf eine externe API oder langsame DB wartet.
|
||||
- Bei vielen gleichzeitigen Anfragen leiden Durchsatz und Antwortzeit.
|
||||
|
||||
FastAPI setzt auf **ASGI** (Asynchronous Server Gateway Interface) und `async def`:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
import httpx # asynchroner HTTP-Client
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/external")
|
||||
async def call_external_api():
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get("https://httpbin.org/get")
|
||||
return response.json()
|
||||
```
|
||||
|
||||
Vorteil:
|
||||
|
||||
- Viele I/O-lastige Requests können parallel abgewickelt werden.
|
||||
- Besonders sinnvoll bei Microservices, die viel mit anderen Services kommunizieren.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Abhängigkeiten und Wiederverwendbarkeit (Dependency Injection)
|
||||
|
||||
FastAPI bietet ein eingebautes **Dependency Injection**-System.
|
||||
Beim Entwickeln von APIs brauchst du häufig:
|
||||
|
||||
- Datenbankverbindungen,
|
||||
- Authentifizierungslogik,
|
||||
- Konfigurationsobjekte.
|
||||
|
||||
Ohne System würdest du das überall wiederholen oder global speichern.
|
||||
Mit FastAPI:
|
||||
|
||||
```python
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def get_settings():
|
||||
# z. B. Konfiguration laden
|
||||
return {"app_name": "Meine App"}
|
||||
|
||||
@app.get("/info")
|
||||
def read_info(settings = Depends(get_settings)):
|
||||
return {"app_name": settings["app_name"]}
|
||||
```
|
||||
|
||||
Das verbessert:
|
||||
|
||||
- Testbarkeit (du kannst Dependencies im Test austauschen),
|
||||
- Struktur deines Codes (klarere Trennung von Zuständigkeiten).
|
||||
|
||||
---
|
||||
|
||||
## 4. Typische Einsatzszenarien (praxisnah)
|
||||
|
||||
### Beispiel 1: Einfaches CRUD für ein „Item“
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
price: float
|
||||
|
||||
# „Fake-Datenbank“ im Speicher
|
||||
items_db: List[Item] = []
|
||||
|
||||
@app.post("/items", response_model=Item)
|
||||
def create_item(item: Item):
|
||||
# einfache Prüfung: ID darf nicht doppelt sein
|
||||
if any(existing.id == item.id for existing in items_db):
|
||||
raise HTTPException(status_code=400, detail="Item ID already exists")
|
||||
items_db.append(item)
|
||||
return item
|
||||
|
||||
@app.get("/items", response_model=List[Item])
|
||||
def list_items():
|
||||
return items_db
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
def get_item(item_id: int):
|
||||
for item in items_db:
|
||||
if item.id == item_id:
|
||||
return item
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
```
|
||||
|
||||
Du bekommst:
|
||||
|
||||
- JSON-APIs für CRUD,
|
||||
- automatische Dokumentation,
|
||||
- automatische Validierung für `Item`.
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 2: Path-Parameter, Query-Parameter, Body
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class SearchFilters(BaseModel):
|
||||
min_price: Optional[float] = None
|
||||
max_price: Optional[float] = None
|
||||
|
||||
@app.get("/products/{category}")
|
||||
def search_products(
|
||||
category: str,
|
||||
q: Optional[str] = None, # Query-Parameter ?q=Text
|
||||
filters: SearchFilters = None # Body JSON
|
||||
):
|
||||
return {
|
||||
"category": category,
|
||||
"query": q,
|
||||
"filters": filters
|
||||
}
|
||||
```
|
||||
|
||||
Beispiel-Request:
|
||||
|
||||
- `GET /products/books?q=python` mit JSON-Body:
|
||||
```json
|
||||
{
|
||||
"min_price": 10,
|
||||
"max_price": 50
|
||||
}
|
||||
```
|
||||
|
||||
FastAPI erkennt:
|
||||
|
||||
- `category` als Pfadparameter,
|
||||
- `q` als Query-Parameter,
|
||||
- `filters` als JSON-Body und validiert ihn.
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 3: Einfache Authentifizierung per Token
|
||||
|
||||
```python
|
||||
from fastapi import Depends, FastAPI, HTTPException, Header
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def get_current_user(x_token: str = Header(...)):
|
||||
if x_token != "secrettoken123":
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing token")
|
||||
return {"username": "alice"}
|
||||
|
||||
@app.get("/profile")
|
||||
def read_profile(current_user = Depends(get_current_user)):
|
||||
return {"message": f"Hello, {current_user['username']}"}
|
||||
```
|
||||
|
||||
Hier:
|
||||
|
||||
- Der Endpunkt `/profile` verlangt einen HTTP-Header `X-Token`.
|
||||
- FastAPI übernimmt das Zusammenspiel von Header → Dependency → Endpoint-Logik.
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen und typische Stolpersteine
|
||||
|
||||
FastAPI nimmt dir viel ab, aber es gibt einige Themen, die für Einsteiger Hürden sein können:
|
||||
|
||||
### 5.1 Asynchrones Programmieren (`async` / `await`)
|
||||
|
||||
- Wenn du noch nie mit Async gearbeitet hast, ist es ungewohnt:
|
||||
- Wann nutze ich `async def`?
|
||||
- Wo brauche ich `await`?
|
||||
- Was ist „Blocking I/O“?
|
||||
- Du musst darauf achten, dass du **asynchrone Bibliotheken** verwendest, wenn du im Handler `async` einsetzt
|
||||
(z. B. `httpx` statt `requests`, `asyncpg` statt `psycopg2`).
|
||||
|
||||
Wenn du erst mal einsteigst, kannst du auch erst **synchron** (ohne `async`) starten und später umstellen.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Typannotationen und Pydantic verstehen
|
||||
|
||||
FastAPI baut stark auf Typen auf:
|
||||
|
||||
- Für jemanden ohne Erfahrung mit Typannotationen in Python ist das anfangs ungewohnt.
|
||||
- Du musst verstehen:
|
||||
- Wie du eigene Modelle mit `BaseModel` definierst.
|
||||
- Wie optionale Felder mit `Optional[...]` und Standardwerten funktionieren.
|
||||
- Wie Validierung und Fehlernachrichten von [[Pydantic]] aussehen.
|
||||
|
||||
Aber:
|
||||
Der Lerneffekt lohnt sich, weil du insgesamt saubereren, stabileren Code bekommst.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Datenbankintegration
|
||||
|
||||
FastAPI selbst bringt keinen [[ORM]] mit. Du musst wählen:
|
||||
|
||||
- z. B. **[[SQLAlchemy]]**, Tortoise-ORM, Prisma, Gino etc.
|
||||
|
||||
Dabei stellen sich Fragen wie:
|
||||
|
||||
- Wie verwalte ich Datenbank-Sessions pro Request?
|
||||
- Nutze ich die sync- oder async-Variante meiner ORM?
|
||||
- Wie realisiere ich Migrations (alembic, etc.)?
|
||||
|
||||
Es gibt viele Beispielprojekte, aber es ist ein zusätzlicher Schritt im Vergleich zu Django, wo ein [[ORM]] „eingebaut“ ist.
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Deployment / Betrieb
|
||||
|
||||
Für Einsteiger ist der Weg von „läuft lokal“ zu „läuft im Internet“ oft herausfordernd:
|
||||
|
||||
- FastAPI-Anwendung läuft typischerweise mit:
|
||||
- [[uvicorn]] oder [[hypercorn]] (ASGI-Server),
|
||||
- oft hinter einem Reverse Proxy wie [[Nginx]].
|
||||
- Themen:
|
||||
- Logging konfigurieren,
|
||||
- Umgebungsvariablen (Konfiguration),
|
||||
- HTTPS/SSL (z. B. via [[Nginx]]/Let’s Encrypt),
|
||||
- Skalierung (mehrere Worker, z. B. `gunicorn` + `uvicorn.workers.UvicornWorker`).
|
||||
|
||||
Für den Anfang kannst du auch auf Plattformen wie Render, Railway, fly.io, oder Docker + Cloud setzen.
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Versionierung und Wartung großer Projekte
|
||||
|
||||
Bei größeren APIs:
|
||||
|
||||
- Wie strukturiere ich meinen Code?
|
||||
- z. B. mit **Routern** (`APIRouter`) und Modulen.
|
||||
- Wie versioniere ich die API? (`/v1/user`, `/v2/user`, …)
|
||||
- Wie halte ich die Dokumentation aktuell?
|
||||
- FastAPI hilft zwar, aber bei vielen Endpunkten braucht man Konventionen und ggf. zusätzliche Dokumentation.
|
||||
|
||||
Beispiel mit Router:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, APIRouter
|
||||
|
||||
app = FastAPI()
|
||||
items_router = APIRouter(prefix="/items", tags=["items"])
|
||||
|
||||
@items_router.get("/")
|
||||
def list_items():
|
||||
return [{"id": 1, "name": "Item 1"}]
|
||||
|
||||
app.include_router(items_router)
|
||||
```
|
||||
|
||||
So kannst du größere Projekte modular strukturieren.
|
||||
|
||||
---
|
||||
|
||||
## 6. Zusammenfassung
|
||||
|
||||
- **FastAPI** ist ein modernes Framework für **Web-APIs in Python**, fokussiert auf:
|
||||
- hohe **Performance** (async),
|
||||
- **Typen** + automatische Validierung ([[Pydantic]]),
|
||||
- automatische **OpenAPI-/Swagger-Dokumentation**,
|
||||
- gute Developer Experience.
|
||||
|
||||
- Es unterscheidet sich von:
|
||||
- **Flask**: moderner, stärker typisiert, async-first, integrierte Validierung & Doku.
|
||||
- **Django**: kein Full-Stack-Framework, sondern eher API-fokussiert; du kombinierst es mit eigenen Tools für DB, Templates etc.
|
||||
- Node/Express: andere Sprache, andere Ökosysteme; FastAPI besonders stark, wenn du ohnehin Python nutzt.
|
||||
|
||||
- Es löst typische Probleme beim API-Bau:
|
||||
- Validierung von Eingaben,
|
||||
- Dokumentation & Testbarkeit,
|
||||
- Performance bei vielen gleichzeitigen Anfragen,
|
||||
- saubere Struktur durch Dependency Injection.
|
||||
|
||||
- Herausforderungen:
|
||||
- Einstieg in asynchrones Programmieren,
|
||||
- Verständnis von Typannotationen & [[Pydantic]],
|
||||
- separate Auswahl & Integration einer Datenbanklösung,
|
||||
- Deployment & Betrieb.
|
||||
+470
@@ -0,0 +1,470 @@
|
||||
Pydantic ist ein zentrales Werkzeug im heutigen Python-Ökosystem, vor allem im Umfeld von APIs (z.B. [[FastAPI]]), Konfiguration und Datenvalidierung.
|
||||
Im Folgenden bekommst du eine systematische Einführung mit praxisnahen Beispielen.
|
||||
|
||||
**Hinweis:** Die Beispiele orientieren sich an **Pydantic v2** (aktuelle Hauptversion). In v1 ist die Syntax ähnlich, aber es gibt einige Unterschiede (z.B. `@validator` vs. `@field_validator`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundsätzliche Definition: Was ist Pydantic?
|
||||
|
||||
**Kurz:**
|
||||
Pydantic ist eine Bibliothek für **Datenmodelle mit Validierung und Parsing** auf Basis von **Python-Typannotationen**.
|
||||
|
||||
Du beschreibst deine Datenstruktur wie bei einer Klasse mit Typen:
|
||||
- Pydantic prüft zur Laufzeit, ob eingehende Daten diese Struktur erfüllen.
|
||||
- Es konvertiert (parst) Werte soweit wie möglich in die gewünschten Typen.
|
||||
- Es gibt strukturierte Fehlermeldungen aus, wenn etwas nicht passt.
|
||||
- Es kann aus deinen Modellen u.a. **JSON-Schemas** generieren.
|
||||
|
||||
Beispiel – ein einfaches Datenmodell:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from typing import List
|
||||
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
tags: List[str] = []
|
||||
|
||||
# Daten aus einer externen Quelle (z.B. JSON)
|
||||
payload = {
|
||||
"id": "123", # wird zu int konvertiert
|
||||
"name": "Alice",
|
||||
"tags": ["admin", "beta"]
|
||||
}
|
||||
|
||||
user = User(**payload)
|
||||
print(user)
|
||||
print(user.id, type(user.id))
|
||||
|
||||
# Fehlvalidierung
|
||||
try:
|
||||
User(id="abc", name=123)
|
||||
except ValidationError as e:
|
||||
print(e.errors())
|
||||
```
|
||||
|
||||
Wichtige Punkte:
|
||||
- `id` ist als `int` deklariert, ein String `"123"` wird automatisch konvertiert.
|
||||
- Wenn Konvertierung scheitert (z.B. `"abc"` → `int`), erzeugt Pydantic eine **ValidationError** mit detailierten Fehlerinfos.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grundkonzepte von Pydantic (v2)
|
||||
|
||||
### 2.1 BaseModel und Felder
|
||||
|
||||
Alle Modelle erben typically von `BaseModel`:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
class Product(BaseModel):
|
||||
id: int
|
||||
name: str = Field(..., min_length=3, description="Produktname")
|
||||
price: float = Field(ge=0)
|
||||
description: Optional[str] = None
|
||||
```
|
||||
|
||||
- `Field(...)` bedeutet „Pflichtfeld“ mit zusätzlichen Metadaten/Constraints.
|
||||
- `ge=0` = „greater or equal 0“.
|
||||
- `Optional[str] = None` = optionales Feld, default `None`.
|
||||
|
||||
### 2.2 Validierung & Parsing
|
||||
|
||||
Pydantic führt **Validierung und Parsing beim Erstellen** des Modells durch.
|
||||
Man kann explizit „parsen“:
|
||||
|
||||
```python
|
||||
from pydantic import TypeAdapter
|
||||
from typing import List
|
||||
|
||||
# Einzelnes Modell: meistens direkt Model(**data)
|
||||
product = Product(id="1", name="TV", price="999.90")
|
||||
|
||||
# Sammlung von Modellen validieren:
|
||||
ta = TypeAdapter(List[Product])
|
||||
|
||||
data = [
|
||||
{"id": 1, "name": "TV", "price": 999.90},
|
||||
{"id": "2", "name": "Laptop", "price": "1299.50"},
|
||||
]
|
||||
|
||||
products = ta.validate_python(data)
|
||||
print(products)
|
||||
```
|
||||
|
||||
`TypeAdapter` in v2 ersetzt viele frühere `parse_obj_as`-Usecases.
|
||||
|
||||
### 2.3 Serialisierung
|
||||
|
||||
Pydantic-Modelle lassen sich leicht in z.B. JSON-kompatible Strukturen umwandeln:
|
||||
|
||||
```python
|
||||
product = Product(id=1, name="TV", price=999.90)
|
||||
print(product.model_dump()) # dict
|
||||
print(product.model_dump_json()) # JSON-String
|
||||
```
|
||||
|
||||
Man kann steuern:
|
||||
- welche Felder inkludiert/exkludiert werden,
|
||||
- wie verschachtelte Modelle serialisiert werden,
|
||||
- ob Alias-Namen verwendet werden sollen etc.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Konzepten / Bibliotheken
|
||||
|
||||
### 3.1 Pydantic vs. `dataclasses`
|
||||
|
||||
Python `dataclasses`:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class UserDC:
|
||||
id: int
|
||||
name: str
|
||||
```
|
||||
|
||||
- `dataclasses` stellen nur **strukturelle Container** bereit.
|
||||
- Keine automatische Validierung oder Typkonvertierung.
|
||||
- Typannotationen sind rein informativ (für IDE, mypy), nicht enforced.
|
||||
|
||||
Pydantic:
|
||||
|
||||
```python
|
||||
class UserModel(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
```
|
||||
|
||||
- Führt **Validierung & Parsing** beim Erstellen durch.
|
||||
- Gibt strukturierte Fehler aus.
|
||||
- Generiert optional JSON-Schemas.
|
||||
- Basiert auch auf Typannotationen, aber **wertet sie zur Laufzeit aus**.
|
||||
|
||||
Kurz:
|
||||
- `dataclasses`: leichtgewichtige Container.
|
||||
- Pydantic: Container + Validierung + Parsing + Schema.
|
||||
|
||||
### 3.2 Pydantic vs. Marshmallow / Cerberus u.ä.
|
||||
|
||||
- **Marshmallow** ist ebenfalls eine Validierungs-/Serialisierungsbibliothek.
|
||||
- Du definierst Schemas explizit über Felder (z.B. `fields.Int()`) statt über Typannotationen.
|
||||
- Skill: starke Serialisierung/Deserialisierung, aber andere API.
|
||||
|
||||
- **Pydantic**:
|
||||
- Nutzt standardmäßige Python-Typannotationen (nativer für moderne Python-Code).
|
||||
- Sehr eng mit Typing-Ökosystem (mypy, IDEs).
|
||||
- Performance-fokussiert, in v2 mit `pydantic-core` in Rust.
|
||||
|
||||
### 3.3 Pydantic vs. Typing-Features (`TypedDict`, `Protocol`, …)
|
||||
|
||||
- `TypedDict` definiert nur statische Typinformationen für Dictionaries.
|
||||
- Pydantic-Modelle sind **richtige Klassen** mit Methoden, Validierung und Verhalten.
|
||||
|
||||
### 3.4 Pydantic vs. ORMs (z.B. Django Models, SQLAlchemy Models)
|
||||
|
||||
- [[ORM]]-Modelle repräsentieren **Datenbanktabellen** und kümmern sich um **Persistenz** (CRUD, Queries).
|
||||
- Pydantic-Modelle sind **reine Daten- und Validierungsmodelle**, ohne DB-Anbindung.
|
||||
|
||||
In der Praxis:
|
||||
- Du kannst Pydantic-Modelle nutzen, um **Requests/Responses** zu validieren und zu dokumentieren.
|
||||
- ORMs nutzen, um die Daten in der Datenbank zu speichern.
|
||||
|
||||
[[FastAPI]] macht genau das:
|
||||
- Pydantic-Modelle für Request/Response,
|
||||
- SQLAlchemy/SQLModel/etc. für DB.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst Pydantic?
|
||||
|
||||
### 4.1 Validierung externer Daten (APIs, Formulare, Message Queues)
|
||||
|
||||
Externe Daten sind oft:
|
||||
- unvollständig,
|
||||
- im falschen Typ,
|
||||
- fehlerhaft strukturiert.
|
||||
|
||||
Pydantic sorgt für:
|
||||
- Zentral definierte Datenstruktur.
|
||||
- Automatische Validierung bei jedem Eingang.
|
||||
- Konvertierung (z.B. `"123"` → `int`, `"2024-01-01"` → `datetime`).
|
||||
|
||||
Beispiel: Request-Daten einer (pseudo) API:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from typing import List
|
||||
|
||||
class Article(BaseModel):
|
||||
title: str
|
||||
url: HttpUrl
|
||||
tags: List[str] = []
|
||||
|
||||
payload = {
|
||||
"title": "Pydantic Einführung",
|
||||
"url": "https://example.com/pydantic",
|
||||
"tags": ["python", "validation"]
|
||||
}
|
||||
|
||||
article = Article(**payload)
|
||||
print(article)
|
||||
```
|
||||
|
||||
Wenn `url` kein gültiger URL-String ist, kommt eine strukturierte Fehlermeldung.
|
||||
|
||||
### 4.2 Konfiguration und Umgebungsvariablen
|
||||
|
||||
Pydantic kann Konfiguration aus:
|
||||
- Umgebungsvariablen,
|
||||
- `.env`-Dateien,
|
||||
- kwargs,
|
||||
- etc.
|
||||
laden und validieren.
|
||||
|
||||
In v2 nutzt man `pydantic-settings`:
|
||||
|
||||
```python
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
debug: bool = False
|
||||
database_url: str
|
||||
port: int = 8000
|
||||
|
||||
model_config = {
|
||||
"env_file": ".env",
|
||||
"env_prefix": "APP_",
|
||||
}
|
||||
|
||||
settings = AppSettings()
|
||||
print(settings.database_url, settings.debug)
|
||||
```
|
||||
|
||||
- `APP_DATABASE_URL` in der Umgebung oder `.env` wird gelesen.
|
||||
- Falsche Typen werden validiert/konvertiert (z.B. `"true"` → `bool`).
|
||||
- Fehlende Pflichtwerte (z.B. `database_url`) führen zu Fehlern.
|
||||
|
||||
### 4.3 Saubere Domain-Modelle und Business-Logik
|
||||
|
||||
Du kannst Pydantic-Modelle verwenden, um deine Domain-Objekte zu modellieren, inklusive:
|
||||
- Validierung von Invarianten (z.B. Preis > 0, Datum in der Zukunft/ Vergangenheit),
|
||||
- Standardwerte,
|
||||
- abgeleitete Felder.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, field_validator
|
||||
from datetime import datetime
|
||||
|
||||
class Event(BaseModel):
|
||||
name: str
|
||||
start: datetime
|
||||
end: datetime
|
||||
|
||||
@field_validator("end")
|
||||
def end_must_be_after_start(cls, v, info):
|
||||
start = info.data.get("start")
|
||||
if start and v <= start:
|
||||
raise ValueError("end must be after start")
|
||||
return v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen & Stolpersteine
|
||||
|
||||
### 5.1 Performance und Overhead
|
||||
|
||||
- Pydantic führt bei **jedem Instanziieren** eines Modells Validierung/Parsing durch.
|
||||
- Bei sehr großen Datenmengen oder sehr häufigen Instanziierungen kann das Performance kosten.
|
||||
- *Lösung*: gezielt einsetzen, ggf. `model_validate` mit `from_attributes=True` o.Ä., Caching, oder an bestimmten Stellen auf „raw“ Datenstrukturen ausweichen.
|
||||
|
||||
### 5.2 Lax vs. Strict Typen
|
||||
|
||||
Standardmäßig ist Pydantic recht **„freundlich“**:
|
||||
- `"123"` → `int(123)`
|
||||
- `"true"` → `bool(True)` (bei Settings)
|
||||
- `"1.23"` → `float(1.23)`
|
||||
|
||||
Das ist praktisch, kann aber auch unerwartete Effekte haben.
|
||||
|
||||
Du kannst **strict**-Typen verwenden oder striktere Konfiguration:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, StrictInt
|
||||
|
||||
class Model(BaseModel):
|
||||
value: StrictInt
|
||||
|
||||
# Model(value="1") -> ValidationError (keine Autokonvertierung)
|
||||
```
|
||||
|
||||
Oder über `model_config`:
|
||||
|
||||
```python
|
||||
class Model(BaseModel):
|
||||
value: int
|
||||
|
||||
model_config = {
|
||||
"strict": True,
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Umgang mit Optional, Defaults, Required
|
||||
|
||||
Typische Stolperfallen:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Example(BaseModel):
|
||||
a: int # Pflichtfeld
|
||||
b: Optional[int] # „darf None sein“, aber kein Default → ebenfalls Pflichtfeld
|
||||
c: int = 0 # optional, default = 0
|
||||
d: Optional[int] = None # optional, default = None
|
||||
e: int = Field(..., description="explizit required") # Pflichtfeld
|
||||
```
|
||||
|
||||
- `Optional[int]` heißt nur „`int` oder `None`“, nicht automatisch optional im Sinne von „nicht im Input vorhanden“.
|
||||
- „Required“ bedeutet: Feld muss im Input vorhanden sein, außer es gibt einen Default.
|
||||
|
||||
### 5.4 Migration v1 → v2
|
||||
|
||||
Wenn du Codebeispiele im Netz findest, sind viele noch Pydantic v1:
|
||||
- `@validator` wurde größtenteils zu `@field_validator`.
|
||||
- `parse_obj_as` → `TypeAdapter`.
|
||||
- `Config`-Inner-Class → `model_config` oder `ConfigDict`.
|
||||
|
||||
Beim Einstieg: gleich v2-Doku lesen und wählen.
|
||||
|
||||
### 5.5 Komplexe verschachtelte Strukturen
|
||||
|
||||
Pydantic kann sehr komplexe Strukturen validieren (verschachtelte Modelle, Union-Typen, generische Modelle).
|
||||
Herausforderung ist eher das **Verständnis** der Typen und Validierungsreihenfolge.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele
|
||||
|
||||
### 6.1 Verschachtelte Modelle
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
class Address(BaseModel):
|
||||
street: str
|
||||
city: str
|
||||
zip_code: str
|
||||
|
||||
class Customer(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
addresses: List[Address]
|
||||
|
||||
data = {
|
||||
"id": "1",
|
||||
"name": "Bob",
|
||||
"addresses": [
|
||||
{"street": "Main St 1", "city": "Berlin", "zip_code": "10115"},
|
||||
{"street": "Side St 2", "city": "Hamburg", "zip_code": "20095"},
|
||||
]
|
||||
}
|
||||
|
||||
customer = Customer(**data)
|
||||
print(customer)
|
||||
```
|
||||
|
||||
Fehler in einer Adresse werden detailliert auf der jeweiligen „Pfad“-Ebene ausgegeben.
|
||||
|
||||
### 6.2 Feld-Constraints & Metadaten
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Literal
|
||||
|
||||
class Order(BaseModel):
|
||||
id: int
|
||||
status: Literal["open", "paid", "shipped"]
|
||||
quantity: int = Field(gt=0, description="Muss > 0 sein")
|
||||
customer_email: str = Field(pattern=r"[^@]+@[^@]+\.[^@]+")
|
||||
|
||||
order = Order(
|
||||
id=1,
|
||||
status="open",
|
||||
quantity=5,
|
||||
customer_email="test@example.com"
|
||||
)
|
||||
```
|
||||
|
||||
- `Literal` beschränkt mögliche Werte (Enum-artig).
|
||||
- `pattern` (Regex) validiert z.B. einfache E-Mail-Formate.
|
||||
|
||||
### 6.3 Custom Validierung mit `field_validator` und `model_validator`
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, field_validator, model_validator
|
||||
|
||||
class User(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
password_repeat: str
|
||||
|
||||
@field_validator("username")
|
||||
def username_not_empty(cls, v):
|
||||
if not v.strip():
|
||||
raise ValueError("username must not be empty")
|
||||
return v
|
||||
|
||||
@model_validator(mode="after")
|
||||
def passwords_match(self):
|
||||
if self.password != self.password_repeat:
|
||||
raise ValueError("passwords do not match")
|
||||
return self
|
||||
```
|
||||
|
||||
- `field_validator` prüft einzelne Felder.
|
||||
- `model_validator` (v2) hat Zugriff auf das ganze Modell (z.B. um zwei Felder zu vergleichen).
|
||||
|
||||
### 6.4 JSON-Schema / OpenAPI-Integration
|
||||
|
||||
Pydantic kann JSON-Schemas erzeugen, die u.a. von [[FastAPI]] genutzt werden, um automatisch Doku (OpenAPI/Swagger) zu generieren:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
|
||||
print(Item.model_json_schema())
|
||||
```
|
||||
|
||||
Das ausgegebene Schema beschreibt die Struktur, Typen und Constraints – ideal für API-Dokumentation.
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusammenfassung
|
||||
|
||||
- **Definition:** Pydantic ist eine Python-Bibliothek für **Datenmodelle mit Validierung, Parsing und Serialisierung** auf Basis von Typannotationen.
|
||||
- **Abgrenzung:**
|
||||
- Mehr als `dataclasses` (mit Validierung & Parsing).
|
||||
- Nutzt Python-Typing natürlicher als Marshmallow & Co.
|
||||
- Kein ORM, sondern ergänzt diese (oft für API-Schicht).
|
||||
- **Probleme, die gelöst werden:**
|
||||
- Validierung externer Daten (APIs, Config, User-Input).
|
||||
- Typ-sichere Domain-Modelle.
|
||||
- Konfiguration aus Umgebungsvariablen/Dateien inkl. Typenprüfung.
|
||||
- Automatische Generierung von JSON-Schemas (z.B. für APIs).
|
||||
- **Herausforderungen:**
|
||||
- Performance bei massiver Nutzung.
|
||||
- Verständnis von strict vs. lax Typen.
|
||||
- Stolperfallen bei Optional/Defaults.
|
||||
- Versionsunterschiede (v1 vs. v2).
|
||||
+1170
File diff suppressed because it is too large
Load Diff
+368
@@ -0,0 +1,368 @@
|
||||
## 1. Grundidee von `argparse`
|
||||
|
||||
`argparse` ist das Standardmodul in Python, um Kommandozeilen-Argumente zu definieren, zu parsen und automatisch Hilfe-/Usage-Texte zu erzeugen.
|
||||
|
||||
Minimalbeispiel:
|
||||
|
||||
```python
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Ein kleines Beispiel-CLI")
|
||||
parser.add_argument("datei", help="Pfad zur Eingabedatei")
|
||||
args = parser.parse_args()
|
||||
|
||||
print(args.datei)
|
||||
```
|
||||
|
||||
Aufruf:
|
||||
```bash
|
||||
python script.py meine_datei.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Argumente definieren: `ArgumentParser.add_argument`
|
||||
|
||||
### 2.1 Positionsargumente
|
||||
|
||||
- Werden ohne führende `-` oder `--` angegeben.
|
||||
- Reihenfolge ist relevant.
|
||||
|
||||
```python
|
||||
parser.add_argument("quelle", help="Quellpfad")
|
||||
parser.add_argument("ziel", help="Zielpfad")
|
||||
```
|
||||
|
||||
Aufruf:
|
||||
```bash
|
||||
python script.py input.txt output.txt
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Pflichtwerte, die immer gebraucht werden (z.B. Eingabe- und Ausgabedatei).
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Optionale Argumente (Flags / Optionen)
|
||||
|
||||
- Beginnen mit `-` bzw. `--`.
|
||||
- Reihenfolge ist egal.
|
||||
- Können Standardwerte haben.
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Ausführliche Ausgabe aktivieren"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-n", "--anzahl",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Anzahl der Elemente (Standard: 10)"
|
||||
)
|
||||
```
|
||||
|
||||
Aufruf:
|
||||
```bash
|
||||
python script.py input.txt --verbose --anzahl 5
|
||||
# oder kurz:
|
||||
python script.py input.txt -v -n 5
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Konfiguration, optionales Verhalten, Debug/Verbose-Flags, Parameter mit Default.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wichtige Parameter von `add_argument`
|
||||
|
||||
### 3.1 `name` / `flags`
|
||||
|
||||
- Beispiel:
|
||||
- Positional: `"datei"`
|
||||
- Optional: `"-v", "--verbose"`
|
||||
|
||||
```python
|
||||
parser.add_argument("datei")
|
||||
parser.add_argument("-v", "--verbose")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 `type`
|
||||
|
||||
- Convertiert Eingabe in Typ.
|
||||
- Validiert automatisch (bei falschem Typ Fehler + Hilfe).
|
||||
|
||||
```python
|
||||
parser.add_argument("--port", type=int, default=8080)
|
||||
parser.add_argument("--faktor", type=float)
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Numerische Werte, Pfade, eigene Typen (z.B. `Path` aus `pathlib`).
|
||||
|
||||
---
|
||||
|
||||
### 3.3 `default`
|
||||
|
||||
- Standardwert, wenn Argument nicht übergeben wird.
|
||||
|
||||
```python
|
||||
parser.add_argument("--log-level", default="INFO")
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Sinnvolle Defaults, um CLI kompakt zu halten.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 `required`
|
||||
|
||||
- Macht optionale Argumente zwingend erforderlich.
|
||||
|
||||
```python
|
||||
parser.add_argument("--config", required=True)
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Flags/Optionen, die zwingend gesetzt werden müssen (z.B. API-Key, Konfigdatei).
|
||||
|
||||
---
|
||||
|
||||
### 3.5 `help`
|
||||
|
||||
- Beschreibung für die `--help`-Ausgabe.
|
||||
|
||||
```python
|
||||
parser.add_argument("--mode", help="Betriebsmodus: fast oder safe")
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Dokumentation der Optionen (sehr wichtig für Benutzerfreundlichkeit).
|
||||
|
||||
---
|
||||
|
||||
### 3.6 `choices`
|
||||
|
||||
- Schränkt erlaubte Werte ein.
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=["fast", "safe"],
|
||||
default="safe",
|
||||
help="fast = schneller, safe = sicherer"
|
||||
)
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Enum-ähnliche Optionen (z.B. `debug/info/warn/error`, `json/text`).
|
||||
|
||||
---
|
||||
|
||||
### 3.7 `action`
|
||||
|
||||
Steuert, was passiert, wenn das Argument gesetzt wird.
|
||||
|
||||
Häufige Actions:
|
||||
|
||||
1. `store` (Standard)
|
||||
Speichert den Wert (z.B. `--port 8000` → `args.port = 8000`).
|
||||
|
||||
2. `store_true` / `store_false`
|
||||
Boolean-Flag, das `True`/`False` setzt.
|
||||
|
||||
```python
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
```
|
||||
|
||||
3. `append`
|
||||
Fügt mehrere Werte in eine Liste ein.
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"-t", "--tag",
|
||||
action="append",
|
||||
help="Kann mehrfach verwendet werden"
|
||||
)
|
||||
# Aufruf: --tag a --tag b -> args.tag = ["a", "b"]
|
||||
```
|
||||
|
||||
4. `count`
|
||||
Zählt, wie oft ein Flag verwendet wurde.
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="count",
|
||||
default=0,
|
||||
help="Mehrfach verwenden für mehr Details"
|
||||
)
|
||||
# -v -> 1, -vv -> 2 ...
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Flags (bool), Mehrfachangaben (Listen), Verbosity-Level etc.
|
||||
|
||||
---
|
||||
|
||||
### 3.8 `nargs`
|
||||
|
||||
Gibt an, wie viele Werte zu einem Argument gehören.
|
||||
|
||||
Typische Varianten:
|
||||
|
||||
- `nargs=1` → eine Liste mit einem Element
|
||||
- `nargs=2` → genau 2 Werte
|
||||
- `nargs="+"` → mindestens ein Wert
|
||||
- `nargs="*"` → beliebig viele (auch 0)
|
||||
|
||||
```python
|
||||
parser.add_argument("dateien", nargs="+", help="Eine oder mehrere Dateien")
|
||||
parser.add_argument("--koordinaten", nargs=2, type=float, help="x y")
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Mehrere Dateien, Koordinaten, Listen von Werten.
|
||||
|
||||
---
|
||||
|
||||
### 3.9 `metavar`
|
||||
|
||||
- Steuert, wie das Argument im Help-Text angezeigt wird.
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
metavar="DATEI",
|
||||
help="Ausgabedatei"
|
||||
)
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Schöner formatierte Hilfe (statt generischer Namen).
|
||||
|
||||
---
|
||||
|
||||
### 3.10 `dest`
|
||||
|
||||
- Name des Attributes in `args`.
|
||||
|
||||
```python
|
||||
parser.add_argument("-o", "--output", dest="ausgabedatei")
|
||||
# args.ausgabedatei
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Lesbare/konfliktfreie Python-Bezeichner, wenn CLI-Namen nicht ideal sind.
|
||||
|
||||
---
|
||||
|
||||
## 4. Subkommandos: `subparsers`
|
||||
|
||||
Für CLI-Tools mit mehreren Befehlen (ähnlich `git commit`, `git status`).
|
||||
|
||||
```python
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(prog="tool")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# Subkommando: "run"
|
||||
run_parser = subparsers.add_parser("run", help="Job ausführen")
|
||||
run_parser.add_argument("job_id", type=int)
|
||||
|
||||
# Subkommando: "list"
|
||||
list_parser = subparsers.add_parser("list", help="Jobs auflisten")
|
||||
list_parser.add_argument("--status", choices=["open", "done"])
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "run":
|
||||
print(f"Starte Job {args.job_id}")
|
||||
elif args.command == "list":
|
||||
print(f"Liste Jobs mit Status {args.status}")
|
||||
```
|
||||
|
||||
Aufrufe:
|
||||
```bash
|
||||
tool run 42
|
||||
tool list --status open
|
||||
```
|
||||
|
||||
Use-Case:
|
||||
- Umfangreiche Tools mit verschiedenen Befehlen (z.B. Admin-Tools, Deployment-CLI).
|
||||
|
||||
---
|
||||
|
||||
## 5. Automatische Hilfe und Usage
|
||||
|
||||
`argparse` erzeugt automatisch `-h` / `--help`:
|
||||
|
||||
```bash
|
||||
python script.py --help
|
||||
```
|
||||
|
||||
Du bekommst:
|
||||
|
||||
- Beschreibung (`description`)
|
||||
- Liste aller Argumente
|
||||
- Default-Werte (wenn konfiguriert)
|
||||
- Subkommandos (falls vorhanden)
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Konvertiert Dateien in andere Formate."
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Minimaler „Best-Practice“-Skeleton
|
||||
|
||||
```python
|
||||
import argparse
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Beispiel-Tool für argparse"
|
||||
)
|
||||
|
||||
# Positionsargumente
|
||||
parser.add_argument("eingabe", help="Eingabedatei")
|
||||
|
||||
# Optionale Argumente
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
help="Ausgabedatei (Standard: stdout)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Ausführliche Ausgabe"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=["fast", "safe"],
|
||||
default="safe",
|
||||
help="Verarbeitungsmodus (Standard: safe)"
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
if args.verbose:
|
||||
print(f"Starte in Modus {args.mode} mit Eingabe {args.eingabe}")
|
||||
# weitere Logik…
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
+525
@@ -0,0 +1,525 @@
|
||||
Hier eine umfassende, aber für Einsteiger verständliche Einführung in **black**, den „opinionated“ Python-Code-Formatter.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee von *black*
|
||||
|
||||
**Was ist black?**
|
||||
|
||||
- *black* ist ein **automatisches Formatierungs-Tool** für Python.
|
||||
- Es ändert **nur die Formatierung**, nicht die Logik deines Codes.
|
||||
- Es ist **„opinionated“**: Es gibt nur sehr wenige Einstellungen – black entscheidet den Stil für dich.
|
||||
|
||||
**Zentrale Idee:**
|
||||
|
||||
> „*You are not your code style.*“
|
||||
> Statt darüber zu diskutieren, ob ein Leerzeichen hier oder dort besser ist, überlässt du das black.
|
||||
>
|
||||
> Ziel:
|
||||
> - einheitlicher Stil
|
||||
> - weniger Diskussionen in Code Reviews
|
||||
> - Fokus auf inhaltliche Fehler, nicht auf Formatierung
|
||||
|
||||
---
|
||||
|
||||
## 2. Was macht black konkret?
|
||||
|
||||
Black nimmt deinen Python-Code, parst ihn und schreibt ihn nach festen Regeln neu. Beispiele:
|
||||
|
||||
### 2.1 Installation
|
||||
|
||||
```bash
|
||||
pip install black
|
||||
```
|
||||
|
||||
### 2.2 Einfache Nutzung
|
||||
|
||||
Eine einzelne Datei formatieren:
|
||||
|
||||
```bash
|
||||
black main.py
|
||||
```
|
||||
|
||||
Ein ganzes Projekt:
|
||||
|
||||
```bash
|
||||
black .
|
||||
```
|
||||
|
||||
Nur anzeigen, was geändert würde (ohne zu schreiben):
|
||||
|
||||
```bash
|
||||
black --diff --check .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Praxisnahe Formatierungsbeispiele
|
||||
|
||||
### 3.1 Zeilenumbrüche und Einrückung
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
def very_long_function_name(arg1,arg2,arg3,arg4,arg5,arg6,arg7=False,arg8=None):
|
||||
return (arg1+arg2+arg3+arg4+arg5+arg6)
|
||||
```
|
||||
|
||||
**Nachher (black):**
|
||||
|
||||
```python
|
||||
def very_long_function_name(
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
arg5,
|
||||
arg6,
|
||||
arg7=False,
|
||||
arg8=None,
|
||||
):
|
||||
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
|
||||
```
|
||||
|
||||
Was passiert?
|
||||
|
||||
- Argumente werden sauber untereinander geschrieben.
|
||||
- Operatoren (+) werden mit Leerzeichen versehen.
|
||||
- Abschluss-Komma nach dem letzten Argument (hilft bei späteren Änderungen).
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Strings und Anführungszeichen
|
||||
|
||||
Black bevorzugt fast immer **doppelte Anführungszeichen**.
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
name = 'Alice'
|
||||
message = 'Hello, ' + name + '!'
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
```python
|
||||
name = "Alice"
|
||||
message = "Hello, " + name + "!"
|
||||
```
|
||||
|
||||
Ausnahmen:
|
||||
- Wenn ein String doppelte Anführungszeichen enthält, kann black einzelne beibehalten, um weniger zu escapen.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Leerzeichen und Klammern
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
result=(1+2)*3
|
||||
if x==42:
|
||||
print( 'Answer',x )
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
```python
|
||||
result = (1 + 2) * 3
|
||||
if x == 42:
|
||||
print("Answer", x)
|
||||
```
|
||||
|
||||
Black:
|
||||
- fügt Leerzeichen um Operatoren hinzu (`1 + 2`, `x == 42`),
|
||||
- entfernt unnötige Leerzeichen (`print( 'Answer',x )` → `print("Answer", x)`).
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Collections (Listen, Dicts, Sets)
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
config = {"host":"localhost","port":5432,"debug":True}
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
```python
|
||||
config = {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"debug": True,
|
||||
}
|
||||
```
|
||||
|
||||
Vorteile:
|
||||
- Bessere Lesbarkeit
|
||||
- Leicht, neue Einträge hinzuzufügen (wegen Abschluss-Komma).
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Lange Ausdrücke
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
query = session.query(User).filter(User.is_active==True, User.signup_date>=start_date, User.signup_date<=end_date).order_by(User.signup_date.desc())
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
```python
|
||||
query = (
|
||||
session.query(User)
|
||||
.filter(
|
||||
User.is_active == True,
|
||||
User.signup_date >= start_date,
|
||||
User.signup_date <= end_date,
|
||||
)
|
||||
.order_by(User.signup_date.desc())
|
||||
)
|
||||
```
|
||||
|
||||
Black bricht lange Zeilen so um, dass:
|
||||
- sie unter der vorgegebenen Maximalbreite bleiben (standard: 88 Zeichen),
|
||||
- die Struktur des Codes klarer sichtbar wird.
|
||||
|
||||
---
|
||||
|
||||
## 4. Abgrenzung zu verwandten Tools
|
||||
|
||||
### 4.1 Formatter vs. Linter vs. Typprüfer
|
||||
|
||||
- **Formatter** (Formatierer):
|
||||
Passen das **Aussehen** deines Codes an – z. B. black, autopep8, yapf.
|
||||
- **Linter**:
|
||||
Finden mögliche **Fehler, Stilprobleme oder unschöne Konstrukte** – z. B. flake8, pylint, ruff.
|
||||
- **Typprüfer**:
|
||||
Prüfen, ob Typen konsistent sind (z. B. mit `typing`) – z. B. mypy, pyright.
|
||||
|
||||
Black ist **nur** ein Formatter.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Black vs. autopep8
|
||||
|
||||
**autopep8**:
|
||||
|
||||
- Ziel: Code so anpassen, dass er PEP8-konform ist.
|
||||
- Orientierung direkt an den PEP8-Regeln.
|
||||
- Viele Optionen (z. B. bestimmte Checks an- oder abschalten).
|
||||
|
||||
**black**:
|
||||
|
||||
- Ziel: **konsequenter, einheitlicher Stil** – nicht nur PEP8, sondern zusätzliche strenge Regeln.
|
||||
- Sehr wenige Konfigurationsmöglichkeiten (bewusst!).
|
||||
- Output ist oft deutlich „strenger” und einheitlicher als autopep8.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Black vs. yapf
|
||||
|
||||
**yapf**:
|
||||
|
||||
- Google-Tool zur Formatierung von Python.
|
||||
- Sehr konfigurierbar: du kannst deinen Style stark beeinflussen (ähnlich wie bei C++/clang-format).
|
||||
- Mehr Freiheit, aber dadurch auch mehr Diskussionen möglich.
|
||||
|
||||
**black**:
|
||||
|
||||
- „Meine Regeln oder gar nicht“.
|
||||
- Ziel: Diskussionen vermeiden, daher kaum Konfigurationsoptionen.
|
||||
- Sehr stabiler, vorhersehbarer Output.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Black vs. isort
|
||||
|
||||
**isort** ist ein Tool, um **Imports** zu sortieren und zu gruppieren.
|
||||
|
||||
- Sortiert `import`-Zeilen alphabetisch und nach Gruppen:
|
||||
- Standardbibliothek
|
||||
- Third-Party
|
||||
- Projektinterne Module
|
||||
|
||||
Black:
|
||||
|
||||
- Formatiert auch Imports (Zeilenumbrüche, Leerzeichen),
|
||||
- sortiert sie aber **nicht** nach Paketnamen.
|
||||
|
||||
Typischerweise nutzt man:
|
||||
|
||||
```bash
|
||||
isort .
|
||||
black .
|
||||
```
|
||||
|
||||
Oder beides zusammen über Tools wie `ruff` oder `pre-commit`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Probleme löst black?
|
||||
|
||||
### 5.1 Konsistenter Stil in Teams
|
||||
|
||||
Ohne Tool:
|
||||
|
||||
- Jeder schreibt „sein“ Python.
|
||||
- Unterschiedliche Leerzeichen, Umbrüche, String-Stile, etc.
|
||||
- Code wirkt „bunt“ und uneinheitlich.
|
||||
|
||||
Mit black:
|
||||
|
||||
- Jedes Commit, jede Datei, jede Funktion hat denselben Stil.
|
||||
- Neue Teammitglieder lernen schneller, was „üblich“ ist – es ist einfach: das, was black macht.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Weniger Diskussionen in Code Reviews
|
||||
|
||||
Vor black:
|
||||
|
||||
- Kommentare wie: „Bitte hier ein Leerzeichen einfügen.“
|
||||
- „Kannst du die Argumente untereinander schreiben?“
|
||||
- „Wir verwenden eigentlich doppelte Anführungszeichen.“
|
||||
|
||||
Mit black:
|
||||
|
||||
- Reviewer sagen: „Bitte einmal black drüber laufen lassen.“
|
||||
- Fokus liegt auf:
|
||||
- Ist der Algorithmus korrekt?
|
||||
- Sind die Funktionen gut benannt?
|
||||
- Sind Tests vorhanden?
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Bessere Lesbarkeit & Wartbarkeit
|
||||
|
||||
- Lange Zeilen werden sinnvoll umgebrochen.
|
||||
- Verschachtelte Ausdrücke werden strukturiert.
|
||||
- Datensammlungen (Listen, Dicts) werden mehrzeilig und übersichtlich dargestellt.
|
||||
|
||||
Beispiel: Eine unübersichtliche Dict-Liste wird automatisch gut lesbar formatiert.
|
||||
|
||||
**Vorher:**
|
||||
|
||||
```python
|
||||
users=[{"id":1,"name":"Alice","active":True},{"id":2,"name":"Bob","active":False}]
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
```python
|
||||
users = [
|
||||
{"id": 1, "name": "Alice", "active": True},
|
||||
{"id": 2, "name": "Bob", "active": False},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Weniger „Rauschen“ in Git-Diffs
|
||||
|
||||
Manuell Änderungen + Formatierung:
|
||||
|
||||
- Du änderst eine Zeile, formatierst etwas,
|
||||
- der Diff zeigt viele Änderungen, obwohl nur wenig Logik geändert wurde.
|
||||
|
||||
Mit black:
|
||||
|
||||
- Wenn alle Dateien bereits formatiert sind, entstehen bei späteren Änderungen klarere Diffs:
|
||||
- Format ist überall gleich,
|
||||
- nur die wirklich geänderte Logik fällt auf.
|
||||
|
||||
---
|
||||
|
||||
## 6. Herausforderungen und typische Stolpersteine
|
||||
|
||||
### 6.1 Einstieg in ein bestehendes Projekt
|
||||
|
||||
Problem:
|
||||
|
||||
- Du führst black in einem **alten, großen Projekt** ein.
|
||||
- Beim ersten Durchlauf ändert black Hunderte/tausende Dateien.
|
||||
- Git-Diff ist riesig.
|
||||
|
||||
Lösungen / Best Practices:
|
||||
|
||||
- Einmaliger „Formatting-Commit“ (nur Style):
|
||||
- In einem eigenen Commit alle Dateien mit black formatieren.
|
||||
- Danach neue Commits nur mit funktionalen Anpassungen.
|
||||
- Oder schrittweise:
|
||||
- Nur neue/aktuell bearbeitete Module mit black formatieren.
|
||||
- z. B. mit `pre-commit`-Hook nur geänderte Dateien behandeln.
|
||||
|
||||
---
|
||||
|
||||
### 6.2 „Mir gefällt der Stil nicht!“
|
||||
|
||||
Black ist sehr strikt:
|
||||
- Du kannst nicht „mal eben“ sagen:
|
||||
- „Ich möchte lieber 120 statt 88 Zeichen pro Zeile“ (ok, das **geht** als Option)
|
||||
- Aber: Du kannst nicht festlegen, wie exakt bestimmte Konstrukte formatiert werden.
|
||||
|
||||
Beispiel: Viele stören sich zunächst an:
|
||||
- doppelten Anführungszeichen,
|
||||
- „ungewöhnlichen“ Zeilenumbrüchen.
|
||||
|
||||
Wichtig:
|
||||
- black ist ein **Team-Tool**.
|
||||
- Man einigt sich darauf, den Stil zu akzeptieren, um Diskussionen zu vermeiden.
|
||||
|
||||
---
|
||||
|
||||
### 6.3 Integration mit anderen Tools (z. B. isort, flake8, ruff)
|
||||
|
||||
Typische Stolperfallen:
|
||||
|
||||
- `isort` und `black` können sich widersprechen, wenn sie unterschiedliche Maximalzeilenlängen nutzen.
|
||||
- Linter können sich über Formatierung beschweren, wenn sie anders konfiguriert sind als black.
|
||||
|
||||
Best Practice:
|
||||
|
||||
- Einheitliche Konfiguration, z. B. in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 88
|
||||
```
|
||||
|
||||
So passen Formatierung und Importsortierung zusammen.
|
||||
|
||||
---
|
||||
|
||||
### 6.4 Änderungen zwischen Black-Versionen
|
||||
|
||||
Black entwickelt sich weiter.
|
||||
|
||||
- In seltenen Fällen ändert eine neue Version den Stil leicht.
|
||||
- Dann kann ein erneuter Durchlauf große Diffs erzeugen.
|
||||
|
||||
Abhilfe:
|
||||
|
||||
- Black-Version in `pyproject.toml` oder `requirements.txt` fest pinnen:
|
||||
|
||||
```text
|
||||
black==24.4.2
|
||||
```
|
||||
|
||||
- Gelegentlich bewusst aktualisieren und einmaliger Reformatting-Commit.
|
||||
|
||||
---
|
||||
|
||||
### 6.5 Performance bei sehr großen Projekten
|
||||
|
||||
Für normale Projekte ist black schnell genug.
|
||||
Bei **sehr großen Repositories** kann einmaliges Formatieren aber dauern.
|
||||
|
||||
Lösungen:
|
||||
|
||||
- Nur geänderte Dateien formatieren (z. B. über `pre-commit`).
|
||||
- In CI nur `black --check .` laufen lassen (prüft, ob alles formatiert ist, ohne neu zu schreiben).
|
||||
|
||||
---
|
||||
|
||||
## 7. Black in der Praxis: Workflows
|
||||
|
||||
### 7.1 Integration in den Editor/IDE
|
||||
|
||||
Die meisten Editoren können black beim Speichern ausführen:
|
||||
|
||||
- **VS Code**:
|
||||
- Erweiterung „Python“ installieren.
|
||||
- In `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
```
|
||||
|
||||
- **PyCharm**:
|
||||
- Externes Tool konfigurieren oder über Plugins.
|
||||
|
||||
Vorteil:
|
||||
- Du denkst nicht mehr über Formatierung nach: Speichern = fertig formatiert.
|
||||
|
||||
---
|
||||
|
||||
### 7.2 Verwendung mit `pre-commit`
|
||||
|
||||
`pre-commit` ist ein Tool, das Skripte ausführt, bevor ein Git-Commit akzeptiert wird.
|
||||
|
||||
Installieren:
|
||||
|
||||
```bash
|
||||
pip install pre-commit
|
||||
```
|
||||
|
||||
In `.pre-commit-config.yaml`:
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.4.2 # Version anpassen
|
||||
hooks:
|
||||
- id: black
|
||||
```
|
||||
|
||||
Aktivieren:
|
||||
|
||||
```bash
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
Jetzt wird bei jedem `git commit` black auf die geänderten Dateien angewandt.
|
||||
Das stellt sicher, dass alles im Repo immer formatiert ist.
|
||||
|
||||
---
|
||||
|
||||
### 7.3 Black in der CI-Pipeline
|
||||
|
||||
In CI-Systemen (GitHub Actions, GitLab CI, etc.) kannst du prüfen, ob der Code schon formatiert ist.
|
||||
|
||||
Beispiel (CLI):
|
||||
|
||||
```bash
|
||||
black --check .
|
||||
```
|
||||
|
||||
- Exit-Code 0: alles ok.
|
||||
- Exit-Code ≠ 0: irgendetwas müsste formatiert werden.
|
||||
|
||||
Das verhindert, dass unformatierter Code in `main` oder `master` landet.
|
||||
|
||||
---
|
||||
|
||||
## 8. Zusammenfassung
|
||||
|
||||
- **black** ist ein **sehr strenger, kaum konfigurierbarer Formatter** für Python-Code.
|
||||
- Ziel:
|
||||
- **einheitlicher Stil**,
|
||||
- **keine Diskussionen** über Formatierung,
|
||||
- Fokus auf Logik und Qualität statt auf Leerzeichen.
|
||||
- Abgrenzung:
|
||||
- black formatiert nur – es ist kein Linter und kein Typprüfer.
|
||||
- Im Vergleich zu autopep8/yapf: weniger konfigurierbar, dafür klarer, einheitlicher Stil.
|
||||
- Es löst:
|
||||
- Stil-Inkonsistenz, Formatierungs-Diskussionen, „Rauschen“ in Diffs.
|
||||
- Herausforderungen:
|
||||
- Einstieg in ältere Projekte,
|
||||
- wenig Einfluss auf Stil,
|
||||
- Abstimmung mit anderen Tools, Versionsänderungen.
|
||||
- In der Praxis:
|
||||
- Nutze black im Editor (Format-on-save),
|
||||
- im `pre-commit`-Hook,
|
||||
- und als `--check` in der CI.
|
||||
|
||||
Wenn du möchtest, kann ich dir im nächsten Schritt ein kleines Beispielprojekt konstruieren (mit `pyproject.toml`, `pre-commit`, isort/black-Konfiguration), damit du siehst, wie man black von Anfang an sauber einrichtet.
|
||||
+640
@@ -0,0 +1,640 @@
|
||||
Mypy ist ein statischer Typprüfer („Type Checker“) für Python. Er hilft dir, Fehler schon beim Schreiben bzw. vor dem Ausführen des Codes zu finden – ähnlich wie ein Compiler in streng typisierten Sprachen – ohne dass du Python als dynamische Sprache „aufgibst“.
|
||||
|
||||
Ich gehe Schritt für Schritt durch:
|
||||
|
||||
1. Grundidee (für Einsteiger verständlich)
|
||||
2. Kurzer praktischer Einstieg (Installation, erste Checks, einfache Beispiele)
|
||||
3. Welche Probleme mypy löst
|
||||
4. Abgrenzung zu ähnlichen / verwandten Tools
|
||||
5. Herausforderungen und typische Stolpersteine
|
||||
6. Praxisnahe Beispiele und Patterns
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee: Was macht mypy?
|
||||
|
||||
Python ist dynamisch typisiert: Variablen haben zur Laufzeit Typen, aber der Interpreter prüft sie nicht im Voraus. Viele Fehler sieht man erst, wenn der entsprechende Code ausgeführt wird.
|
||||
|
||||
Mypy ändert daran nichts zur Laufzeit – aber es analysiert deinen Code **statisch** (also ohne ihn auszuführen) und prüft, ob die verwendeten **Typannotationen** konsistent sind.
|
||||
|
||||
### Typannotationen – ein Beispiel
|
||||
|
||||
Ohne Typen:
|
||||
|
||||
```python
|
||||
def add(a, b):
|
||||
return a + b
|
||||
```
|
||||
|
||||
Das ist legal, aber du kannst aus dem Code nicht erkennen, ob `a` und `b` Zahlen, Strings oder etwas anderes sein sollen. Python lässt vieles zu:
|
||||
|
||||
```python
|
||||
print(add(1, 2)) # 3
|
||||
print(add("a", "b")) # "ab"
|
||||
print(add(1, "b")) # TypeError zur Laufzeit
|
||||
```
|
||||
|
||||
Mit Typannotationen:
|
||||
|
||||
```python
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
```
|
||||
|
||||
Damit sagst du: `add` nimmt zwei `int` und gibt einen `int` zurück.
|
||||
|
||||
Mypy überprüft jetzt:
|
||||
|
||||
```bash
|
||||
mypy mein_code.py
|
||||
```
|
||||
|
||||
und meldet z.B.:
|
||||
|
||||
```text
|
||||
mein_code.py:10: error: Argument 2 to "add" has incompatible type "str"; expected "int"
|
||||
```
|
||||
|
||||
– wenn du irgendwo `add(1, "b")` aufrufst.
|
||||
|
||||
**Grundidee**:
|
||||
Du schreibst „Verträge“ (Typen) in deinen Code, und mypy überprüft, ob du dich überall daran hältst. Das verbessert Lesbarkeit, Robustheit und macht refactoring sicherer.
|
||||
|
||||
---
|
||||
|
||||
## 2. Kurzer praktischer Einstieg
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install mypy
|
||||
```
|
||||
|
||||
(Je nach Setup evtl. in einer virtuellen Umgebung.)
|
||||
|
||||
### Minimalbeispiel
|
||||
|
||||
`calculator.py`:
|
||||
|
||||
```python
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
def main() -> None:
|
||||
x = add(1, 2)
|
||||
y = add("a", "b") # Fehler
|
||||
|
||||
print(x, y)
|
||||
```
|
||||
|
||||
Mypy ausführen:
|
||||
|
||||
```bash
|
||||
mypy calculator.py
|
||||
```
|
||||
|
||||
Ausgabe:
|
||||
|
||||
```text
|
||||
calculator.py:6: error: Argument 1 to "add" has incompatible type "str"; expected "int"
|
||||
Found 1 error in 1 file (checked 1 source file)
|
||||
```
|
||||
|
||||
Obwohl Python den Code ausführen würde (und bei `add("a", "b")` sogar ein „korrektes“ Ergebnis liefern würde: `"ab"`), sagt mypy: Du hast gegen deinen eigenen Typvertrag verstoßen.
|
||||
|
||||
### Gradual Typing
|
||||
|
||||
Du musst nicht alles von Anfang an typisieren. Du kannst Schritt für Schritt anfangen:
|
||||
|
||||
```python
|
||||
def add(a, b): # keine Typen hier
|
||||
return a + b
|
||||
|
||||
def use_add() -> int:
|
||||
result = add(1, 2) # mypy lässt das oft durchgehen (je nach Konfiguration)
|
||||
return result
|
||||
```
|
||||
|
||||
Mypy arbeitet „gradual“:
|
||||
|
||||
- Ungetypte Bereiche werden als `Any` betrachtet (unsicher, aber flexibel).
|
||||
- Getypte Bereiche werden überprüft.
|
||||
- Du kannst nach und nach mehr Typen hinzufügen und die Strenge erhöhen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Welche Probleme werden durch mypy gelöst?
|
||||
|
||||
### 3.1. Typbezogene Fehler früh erkennen
|
||||
|
||||
Typische Klassen von Bugs:
|
||||
|
||||
1. **Falsche Argumenttypen**:
|
||||
|
||||
```python
|
||||
def send_email(to: str, subject: str, body: str) -> None:
|
||||
...
|
||||
|
||||
send_email(["user@example.com"], "Hi", "Text") # Bug: Liste statt String
|
||||
```
|
||||
|
||||
Mypy:
|
||||
|
||||
```text
|
||||
error: Argument 1 to "send_email" has incompatible type "List[str]"; expected "str"
|
||||
```
|
||||
|
||||
2. **Verfügbare Attribute/Methoden**:
|
||||
|
||||
```python
|
||||
def greet(name: str) -> None:
|
||||
print(name.upper())
|
||||
|
||||
user_name: int = 42
|
||||
greet(user_name)
|
||||
```
|
||||
|
||||
Mypy:
|
||||
|
||||
```text
|
||||
error: Argument 1 to "greet" has incompatible type "int"; expected "str"
|
||||
```
|
||||
|
||||
3. **Optionale Werte vergessen zu prüfen** (`None`):
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
def get_user_name(user_id: int) -> Optional[str]:
|
||||
...
|
||||
|
||||
def print_name(user_id: int) -> None:
|
||||
name = get_user_name(user_id)
|
||||
print(name.upper()) # Bug: name kann None sein!
|
||||
```
|
||||
|
||||
Mypy:
|
||||
|
||||
```text
|
||||
error: Item "None" of "Optional[str]" has no attribute "upper"
|
||||
```
|
||||
|
||||
Du wirst gezwungen, zuerst auf `None` zu prüfen:
|
||||
|
||||
```python
|
||||
def print_name(user_id: int) -> None:
|
||||
name = get_user_name(user_id)
|
||||
if name is None:
|
||||
print("User not found")
|
||||
return
|
||||
print(name.upper()) # jetzt ok
|
||||
```
|
||||
|
||||
### 3.2. Sicherere Refactorings
|
||||
|
||||
Wenn du Funktionensignaturen änderst, Parameter umbenennst oder Rückgabetypen anpasst, kann mypy dir helfen, alle Stellen zu finden, die du anpassen musst.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
# vorher
|
||||
def get_price(product_id: int) -> float:
|
||||
...
|
||||
|
||||
# nachher
|
||||
def get_price(product_id: int) -> int: # Rückgabetyp geändert!
|
||||
...
|
||||
```
|
||||
|
||||
Wenn irgendwo angenommen wird, dass `float` zurückkommt:
|
||||
|
||||
```python
|
||||
price_cents: float = get_price(123) # jetzt inkonsistent
|
||||
```
|
||||
|
||||
meldet mypy das. Das verringert das Risiko von subtilen Bugs nach Refactorings.
|
||||
|
||||
### 3.3. Bessere Dokumentation & IDE-Unterstützung
|
||||
|
||||
Typannotationen sind lebende Dokumentation:
|
||||
|
||||
```python
|
||||
def load_config(path: str) -> dict[str, str]:
|
||||
...
|
||||
```
|
||||
|
||||
Du siehst sofort, was die Funktion erwartet und liefert – ohne lange Kommentare. IDEs nutzen die Typen für:
|
||||
|
||||
- Autovervollständigung
|
||||
- Inlay Hints
|
||||
- Navigation („go to definition“)
|
||||
- Inline-Fehlermeldungen
|
||||
|
||||
---
|
||||
|
||||
## 4. Abgrenzung zu ähnlichen / verwandten Tools
|
||||
|
||||
### 4.1. Mypy vs. Linter (z.B. flake8, pylint)
|
||||
|
||||
**Linter** prüfen v.a.:
|
||||
|
||||
- Stil (PEP 8)
|
||||
- potenziell problematische Patterns (unbenutzte Variablen, Schatten von Builtins, zu komplexe Funktionen)
|
||||
- gewisse Logikfehler (z.B. nie erreichte Codezweige)
|
||||
|
||||
**Mypy** fokussiert auf **Typkonsistenz**:
|
||||
|
||||
- Stimmen die deklarierten Typen mit den tatsächlichen Verwendungen überein?
|
||||
- Können bestimmte Codezweige überhaupt erreicht werden, wenn Typen berücksichtigt werden?
|
||||
- Sind Operationen auf bestimmten Typen erlaubt?
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
x = []
|
||||
x.append(1)
|
||||
x.append("a")
|
||||
```
|
||||
|
||||
Linter: meistens kein Problem.
|
||||
Mypy (je nach Typinferenz) könnte sagen:
|
||||
|
||||
```text
|
||||
List item 1 has incompatible type "str"; expected "int"
|
||||
```
|
||||
|
||||
Fazit:
|
||||
Linter und mypy ergänzen sich – sie ersetzen sich nicht.
|
||||
|
||||
### 4.2. Mypy vs. Testframeworks (pytest, unittest)
|
||||
|
||||
**Tests**:
|
||||
|
||||
- prüfen Laufzeitverhalten für konkrete Eingaben.
|
||||
- stellen sicher, dass Funktionen das tun, was fachlich / funktional gewünscht ist.
|
||||
|
||||
**Mypy**:
|
||||
|
||||
- prüft nur Typkonsistenz – keine fachliche Korrektheit.
|
||||
- findet z.B. nicht, ob du die falsche mathematische Formel verwendest, solange die Typen passen.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
def calculate_discount(price: float) -> float:
|
||||
return price * 2 # fachlich falsch, aber typgerecht
|
||||
```
|
||||
|
||||
Mypy ist zufrieden. Ein Unit-Test würde diesen Fehler finden.
|
||||
|
||||
Fazit:
|
||||
Mypy ergänzt Tests, ersetzt sie aber nicht.
|
||||
|
||||
### 4.3. Mypy vs. andere Typechecker (Pyright, Pyre, pytype)
|
||||
|
||||
Es gibt mehrere Typchecker für Python:
|
||||
|
||||
- **mypy** – der „Klassiker“, in Python geschrieben, von vielen Projekten verwendet.
|
||||
- **pyright** – sehr schneller Typechecker (Microsoft), in TypeScript geschrieben.
|
||||
- **pyre** – von Meta (Facebook), mit Fokus auf große Codebasen.
|
||||
- **pytype** – von Google.
|
||||
|
||||
Sie verfolgen alle eine ähnliche Idee: statische Typprüfung für Python. Unterschiede gibt es bei:
|
||||
|
||||
- Performance
|
||||
- Genauigkeit / Strenge in bestimmten Bereichen
|
||||
- Tooling-Integration (z.B. VS Code nutzt intern Pyright)
|
||||
|
||||
Für den Einstieg ist mypy völlig ausreichend und weit verbreitet.
|
||||
|
||||
### 4.4. Mypy vs. Laufzeit-Typprüfung (pydantic, marshmallow)
|
||||
|
||||
**pydantic** & Co.:
|
||||
|
||||
- validieren und konvertieren Daten zur **Laufzeit** (z.B. JSON-Input in API).
|
||||
- werfen Exceptions, wenn Daten nicht passen.
|
||||
- nutzen Typannotationen als Basis, sind aber nicht auf Compile-/Check-Zeit beschränkt.
|
||||
|
||||
**Mypy**:
|
||||
|
||||
- prüft nur zur Analysezeit, ändert das Laufzeitverhalten nicht.
|
||||
- „merkt nicht“, ob zur Laufzeit echte Validierung stattfindet.
|
||||
|
||||
Beispiel mit pydantic:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
user = User(id="123", name="Alice") # zur Laufzeit wird "123" zu int geparst
|
||||
```
|
||||
|
||||
Mypy würde melden:
|
||||
|
||||
```text
|
||||
Argument "id" to "User" has incompatible type "str"; expected "int"
|
||||
```
|
||||
|
||||
– obwohl pydantic das zur Laufzeit akzeptiert und konvertiert.
|
||||
Hier musst du entscheiden, ob du dich eher am statischen Vertrag (Typ) oder am dynamischen Verhalten orientieren willst.
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen und typische Stolpersteine
|
||||
|
||||
### 5.1. Legacy Code ohne Typen
|
||||
|
||||
In bestehendem Code fehlen oft Typannotationen, und vieles ist dynamisch.
|
||||
|
||||
Strategien:
|
||||
|
||||
- Zuerst nur neue Modules/Funktionen typisieren.
|
||||
- Mypy mit „lockereren“ Einstellungen starten.
|
||||
- Langsam „strictness“ erhöhen.
|
||||
|
||||
Beispiel-Konfiguration (`mypy.ini`):
|
||||
|
||||
```ini
|
||||
[mypy]
|
||||
python_version = 3.11
|
||||
ignore_missing_imports = True
|
||||
disallow_untyped_defs = False
|
||||
disallow_incomplete_defs = False
|
||||
```
|
||||
|
||||
Später kannst du verschärfen:
|
||||
|
||||
```ini
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
warn_unused_ignores = True
|
||||
strict_optional = True
|
||||
```
|
||||
|
||||
### 5.2. Dynamische Features von Python
|
||||
|
||||
Dinge wie:
|
||||
|
||||
- dynamisches Hinzufügen von Attributen
|
||||
- `setattr`, `getattr`
|
||||
- Metaklassen-Magie
|
||||
- Monkey-Patching
|
||||
|
||||
sind schwer für statische Analyser.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
class Dynamic:
|
||||
pass
|
||||
|
||||
obj = Dynamic()
|
||||
obj.name = "Alice" # dynamisches Attribut
|
||||
print(obj.name)
|
||||
```
|
||||
|
||||
Mypy weiß nicht, dass `name` existiert, und meldet:
|
||||
|
||||
```text
|
||||
error: "Dynamic" has no attribute "name"
|
||||
```
|
||||
|
||||
Workarounds:
|
||||
|
||||
- Attribut im Klassendefinitionskörper deklarieren:
|
||||
|
||||
```python
|
||||
class Dynamic:
|
||||
name: str
|
||||
```
|
||||
- oder `# type: ignore` an problematischen Stellen nutzen.
|
||||
|
||||
### 5.3. Komplexe Typen und Verbosität
|
||||
|
||||
Generics, `Union`, `Optional`, `TypedDict`, `Protocol` etc. können komplex werden. Das kostet Einarbeitung.
|
||||
|
||||
Beispiel für generische Funktion:
|
||||
|
||||
```python
|
||||
from typing import TypeVar, Iterable, List
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def first(items: Iterable[T]) -> T:
|
||||
for item in items:
|
||||
return item
|
||||
raise ValueError("Empty iterable")
|
||||
```
|
||||
|
||||
Mypy hilft hier, allgemeingültige, typsichere Utilities zu schreiben, aber das Typ-System wird relativ mächtig (und gelegentlich inelegant).
|
||||
|
||||
### 5.4. False Positives und `# type: ignore`
|
||||
|
||||
Manchmal **weißt du mehr** als mypy. Dann musst du mit mypy kommunizieren:
|
||||
|
||||
```python
|
||||
from typing import cast, Any
|
||||
|
||||
def get_from_json(json_obj: dict[str, Any]) -> int:
|
||||
return cast(int, json_obj["value"])
|
||||
```
|
||||
|
||||
oder:
|
||||
|
||||
```python
|
||||
some_weird_library_call() # type: ignore[arg-type]
|
||||
```
|
||||
|
||||
Zu viele `# type: ignore` können aber wieder die Sicherheit untergraben. Es lohnt sich, sie sparsam und begründet einzusetzen.
|
||||
|
||||
### 5.5. Performance bei großen Codebasen
|
||||
|
||||
Für wirklich große Projekte kann mypy langsamer werden, vor allem bei vielen Imports und tiefen Typstrukturen.
|
||||
Es gibt Optionen wie `--incremental` und `dmypy` (Daemon-Modus), um das zu beschleunigen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele & Patterns
|
||||
|
||||
### 6.1. Basic Typannotationen
|
||||
|
||||
```python
|
||||
def greet(name: str, times: int = 1) -> None:
|
||||
for _ in range(times):
|
||||
print(f"Hello, {name}!")
|
||||
```
|
||||
|
||||
Sammlungstypen:
|
||||
|
||||
```python
|
||||
from typing import List, Dict
|
||||
|
||||
def total_length(names: List[str]) -> int:
|
||||
length = 0
|
||||
for n in names:
|
||||
length += len(n)
|
||||
return length
|
||||
|
||||
def invert_mapping(mapping: Dict[int, str]) -> Dict[str, int]:
|
||||
return {v: k for k, v in mapping.items()}
|
||||
```
|
||||
|
||||
Ab Python 3.9 kannst du oft die Kurzform nutzen:
|
||||
|
||||
```python
|
||||
def total_length(names: list[str]) -> int:
|
||||
...
|
||||
def invert_mapping(mapping: dict[int, str]) -> dict[str, int]:
|
||||
...
|
||||
```
|
||||
|
||||
### 6.2. Optional und Union
|
||||
|
||||
```python
|
||||
from typing import Optional, Union
|
||||
|
||||
def parse_int(value: str) -> Optional[int]:
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def stringify(value: Union[int, float]) -> str:
|
||||
return f"{value:.2f}"
|
||||
```
|
||||
|
||||
Aufruf:
|
||||
|
||||
```python
|
||||
result = parse_int("123")
|
||||
if result is not None:
|
||||
print(result + 1)
|
||||
```
|
||||
|
||||
Mypy zwingt dich, mit dem `None`-Fall umzugehen.
|
||||
|
||||
### 6.3. Typen für Klassen
|
||||
|
||||
```python
|
||||
class User:
|
||||
def __init__(self, user_id: int, name: str) -> None:
|
||||
self.user_id = user_id
|
||||
self.name = name
|
||||
|
||||
def greet(self) -> str:
|
||||
return f"Hello, {self.name}!"
|
||||
```
|
||||
|
||||
### 6.4. Dataclasses mit Typen
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Product:
|
||||
id: int
|
||||
name: str
|
||||
price_cents: int
|
||||
|
||||
def apply_discount(product: Product, percent: float) -> Product:
|
||||
discount = int(product.price_cents * percent / 100)
|
||||
return Product(
|
||||
id=product.id,
|
||||
name=product.name,
|
||||
price_cents=product.price_cents - discount
|
||||
)
|
||||
```
|
||||
|
||||
Mypy prüft, ob du `Product` überall korrekt verwendest.
|
||||
|
||||
### 6.5. Typen für „Dictionary-Objekte“: TypedDict
|
||||
|
||||
Wenn du viele Dictionaries mit immer der gleichen Struktur verwendest (z.B. JSON):
|
||||
|
||||
```python
|
||||
from typing import TypedDict
|
||||
|
||||
class UserData(TypedDict):
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
|
||||
def send_welcome_mail(user: UserData) -> None:
|
||||
print(f"Sending mail to {user['email']}")
|
||||
```
|
||||
|
||||
Mypy prüft:
|
||||
|
||||
- fehlen Keys?
|
||||
- stimmen die Typen der Werte?
|
||||
|
||||
### 6.6. Duck Typing mit Protocol
|
||||
|
||||
Du willst „etwas, das wie eine Datei ist“, nicht unbedingt eine konkrete Klasse:
|
||||
|
||||
```python
|
||||
from typing import Protocol
|
||||
|
||||
class Writer(Protocol):
|
||||
def write(self, data: str) -> int:
|
||||
...
|
||||
|
||||
def log_to(writer: Writer, message: str) -> None:
|
||||
writer.write(message + "\n")
|
||||
```
|
||||
|
||||
Jede Klasse, die `write(self, str) -> int` implementiert, passt – ganz im Sinne von „Duck Typing“, aber statisch überprüfbar.
|
||||
|
||||
### 6.7. Mypy-Konfiguration in der Praxis
|
||||
|
||||
`mypy.ini` im Projektverzeichnis:
|
||||
|
||||
```ini
|
||||
[mypy]
|
||||
python_version = 3.11
|
||||
strict = True # aktiviert viele strenge Checks
|
||||
|
||||
# Beispiele für häufige Optionen:
|
||||
# disallow_untyped_defs = True
|
||||
# disallow_incomplete_defs = True
|
||||
# no_implicit_optional = True
|
||||
# warn_unused_ignores = True
|
||||
# warn_return_any = True
|
||||
```
|
||||
|
||||
Du kannst auch per Modulbereich Regeln setzen:
|
||||
|
||||
```ini
|
||||
[mypy-myproject.legacy.*]
|
||||
ignore_errors = True # Legacy-Code vorerst ignorieren
|
||||
|
||||
[mypy-myproject.core.*]
|
||||
strict = True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- **Grundidee**: mypy ist ein statischer Typprüfer für Python. Du annotierst deinen Code mit Typen, und mypy überprüft, ob der Code diese Verträge einhält.
|
||||
- **Probleme, die gelöst werden**:
|
||||
- Frühes Finden von typbezogenen Fehlern
|
||||
- Sicherere Refactorings
|
||||
- Bessere Dokumentation und Tooling/IDE-Unterstützung
|
||||
- **Abgrenzung**:
|
||||
- Linter → Stil & allgemeine Codequalität; mypy → Typkonsistenz
|
||||
- Tests → fachliche Korrektheit zur Laufzeit; mypy → statische Typkonsistenz
|
||||
- Andere Typchecker (Pyright etc.) verfolgen die gleiche Grundidee
|
||||
- Runtime-Validatoren (pydantic) prüfen zur Laufzeit, mypy nur zur Analysezeit
|
||||
- **Herausforderungen**:
|
||||
- Einstieg in bestehende, dynamische Codebasen
|
||||
- Dynamische Python-Features sind schwer statisch zu erfassen
|
||||
- Komplexe Typen können verbose sein
|
||||
- Manchmal sind `cast` und `# type: ignore` nötig
|
||||
|
||||
Wenn du magst, kann ich dir als nächsten Schritt ein kleines Beispielprojekt skizzieren (z.B. eine Mini-API oder ein CLI-Tool) und zeigen, wie man mypy dort konkret einführt und schrittweise verschärft.
|
||||
+1212
File diff suppressed because it is too large
Load Diff
+540
@@ -0,0 +1,540 @@
|
||||
Ruff ist ein relativ neues, sehr schnelles Tool zur Verbesserung der Code-Qualität in Python. Es kombiniert Funktionen, für die man früher mehrere Werkzeuge brauchte (Linter, Formatter, Import-Sortierer, teilweise Sicherheits-Checks), in einem Programm.
|
||||
|
||||
Ich gehe Schritt für Schritt durch:
|
||||
|
||||
1. Grundidee und Zielgruppe
|
||||
2. Was macht ruff konkret?
|
||||
3. Abgrenzung zu ähnlichen Tools
|
||||
4. Welche Probleme löst ruff?
|
||||
5. Typische Herausforderungen im Einsatz
|
||||
6. Praxisnahe Beispiele (inkl. Konfiguration)
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee von ruff – in einfachen Worten
|
||||
|
||||
Stell dir vor, mehrere Entwickler:innen arbeiten an einem Python-Projekt. Ohne Regeln sieht der Code schnell sehr unterschiedlich aus:
|
||||
|
||||
- Mal sind Leerzeichen anders gesetzt,
|
||||
- mal werden nicht genutzte Variablen liegen gelassen,
|
||||
- vielleicht wird an manchen Stellen unsicherer Code geschrieben,
|
||||
- im schlimmsten Fall fallen bestimmte Fehler erst spät auf.
|
||||
|
||||
Bisher nutzte man typischerweise mehrere Tools:
|
||||
|
||||
- ein **Linter** (z. B. *flake8* oder *pylint*),
|
||||
- einen **Formatter** (z. B. *black*),
|
||||
- einen **Import-Sortierer** (z. B. *isort*),
|
||||
- evtl. ein **Security-Tool** (z. B. *bandit*),
|
||||
- plus diverse Plugins.
|
||||
|
||||
**Ruff will das bündeln:**
|
||||
|
||||
- Ein Tool für viele Code-Qualitätsaspekte
|
||||
- Sehr schnell (geschrieben in Rust, nicht in Python)
|
||||
- Einfache Konfiguration (alles typischerweise in `pyproject.toml`)
|
||||
- Viele Checks, die sich an bekannten Tools orientieren (flake8-Plugins, isort-Regeln, bandit-Regeln usw.)
|
||||
|
||||
---
|
||||
|
||||
## 2. Was macht ruff konkret?
|
||||
|
||||
### 2.1. Linting (Fehler und Stilprobleme finden)
|
||||
|
||||
Ruff untersucht deinen Python-Code und meldet z. B.:
|
||||
|
||||
- Syntax-Fehler
|
||||
- Ungenutzte Variablen oder Importe
|
||||
- Logische Stolperfallen (z. B. `== None` statt `is None`)
|
||||
- Stil-Regeln (z. B. maximale Zeilenlänge)
|
||||
- Potenziell unsichere Konstruktionen
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
def calculate(a, b):
|
||||
result = a + b
|
||||
unused = 42
|
||||
if a == None:
|
||||
print("a is None")
|
||||
return result
|
||||
```
|
||||
|
||||
Ruff würde in etwa melden (vereinfacht):
|
||||
|
||||
- `unused` wird nie verwendet
|
||||
- `a == None` – besser `a is None`
|
||||
|
||||
Viele dieser Probleme kann ruff auch **automatisch beheben** (`--fix`).
|
||||
|
||||
---
|
||||
|
||||
### 2.2. Formatierung (Code automatisch „schön“ machen)
|
||||
|
||||
Ruff hat inzwischen einen **eigenen Formatter**, der ähnlich wie *black* funktioniert:
|
||||
|
||||
- Einheitliche Einrückungen und Zeilenumbrüche
|
||||
- Klammer-Formatierung
|
||||
- Konsistente Verwendung von Anführungszeichen (je nach Einstellung)
|
||||
- Entfernung überflüssiger Leerzeilen usw.
|
||||
|
||||
Beispiel (unformatierter Code):
|
||||
|
||||
```python
|
||||
def foo( x:int,y:int )->int:
|
||||
return x+y
|
||||
```
|
||||
|
||||
Nach `ruff format` könnte das so aussehen:
|
||||
|
||||
```python
|
||||
def foo(x: int, y: int) -> int:
|
||||
return x + y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3. Imports sortieren und aufräumen
|
||||
|
||||
Ruff kann ähnlich wie *isort*:
|
||||
|
||||
- Importe sortieren (alphabetisch und nach Gruppen: Standardbibliothek, Drittanbieter, Projektcode)
|
||||
- Unbenutzte Importe entfernen
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
import myproject.utils
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
|
||||
from math import sqrt
|
||||
from math import ceil
|
||||
```
|
||||
|
||||
Nach ruff (vereinfacht):
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
from math import ceil, sqrt
|
||||
|
||||
import requests
|
||||
|
||||
import myproject.utils
|
||||
```
|
||||
|
||||
Und wenn `sys` gar nicht verwendet wird, kann ruff es auch entfernen.
|
||||
|
||||
---
|
||||
|
||||
### 2.4. Ein Tool – mehrere Regel-Sammlungen
|
||||
|
||||
Ruff bringt rule sets mit, die vielen bekannten Tools entsprechen, z. B.:
|
||||
|
||||
- **E/F/W**: Pycodestyle/Pyflakes-ähnlich (via flake8)
|
||||
- **I**: isort-Regeln
|
||||
- **N**: pep8-naming (Namenskonventionen)
|
||||
- **S**: bandit (Sicherheitsregeln)
|
||||
- **UP**: pyupgrade (veraltete Syntax, modernisieren)
|
||||
- u. v. m.
|
||||
|
||||
Du kannst über die Konfiguration steuern, welche Regel-Gruppen du aktivierst oder deaktivierst.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu ähnlichen Tools
|
||||
|
||||
### 3.1. Ruff vs. flake8 (+ Plugins)
|
||||
|
||||
**flake8** ist ein Linter; für viele Extras braucht man Plugins:
|
||||
|
||||
- z. B. `flake8-bugbear`, `flake8-import-order`, `pep8-naming` usw.
|
||||
|
||||
**Ruff:**
|
||||
|
||||
- Bietet die Funktionalität vieler flake8-Plugins „eingebaut“.
|
||||
- Ist deutlich **schneller** (insbesondere bei großen Projekten).
|
||||
- Wird oft als Drop-in-Ersatz für flake8 verwendet.
|
||||
|
||||
Aber:
|
||||
|
||||
- flake8 ist schon lange etabliert; manche Teams haben stark angepasste flake8-Setups, die man nicht 1:1 nach ruff übertragen kann.
|
||||
- Einige Spezial-Plugins existieren ggf. nur für flake8.
|
||||
|
||||
---
|
||||
|
||||
### 3.2. Ruff vs. pylint
|
||||
|
||||
**pylint**:
|
||||
|
||||
- Sehr umfangreicher Linter (viele komplexe Regeln, u. a. über Projektstruktur, OOP-Patterns usw.)
|
||||
- Langsam im Vergleich zu ruff
|
||||
- Detaillierte Reports und Scores
|
||||
|
||||
**Ruff**:
|
||||
|
||||
- Fokus auf **Geschwindigkeit** und auf Regeln, die sich gut automatisieren/auto-fixen lassen.
|
||||
- Viele „klassische“ Lint-Regeln, Naming, Imports, Security-Basics, aber nicht alle tiefgehenden Analysen von pylint.
|
||||
- Für komplexe Architektur-Regeln wird weiterhin oft pylint oder andere Tools genutzt.
|
||||
|
||||
---
|
||||
|
||||
### 3.3. Ruff vs. black
|
||||
|
||||
**black**:
|
||||
|
||||
- Reiner **Formatter** – macht nur Formatierung, keine Lint-Fehler (bis auf ganz wenige Ausnahmen).
|
||||
- Sehr stabile, strikte Formatierung (Meinung: „The uncompromising code formatter“).
|
||||
|
||||
**Ruff**:
|
||||
|
||||
- Eigenständiger Formatter, der in vielen Projekten Black ersetzen kann.
|
||||
- Zusätzlich: Linting, Import-Sortierung, Security-Regeln usw.
|
||||
- Du kannst:
|
||||
- nur ruff als Formatter nutzen, oder
|
||||
- ruff als Linter + black als Formatter (dann `ruff format` nicht verwenden), wenn dein Team bereits stark auf black setzt.
|
||||
|
||||
---
|
||||
|
||||
### 3.4. Ruff vs. isort
|
||||
|
||||
**isort**:
|
||||
|
||||
- Spezialisiert auf das Sortieren von Imports.
|
||||
|
||||
**Ruff**:
|
||||
|
||||
- Hat eine integrierte Import-Sortierung (Regelgruppe `I`).
|
||||
- Für die meisten Fälle reicht ruff völlig aus.
|
||||
- Wenn ihr sehr spezielle Import-Sortierregeln braucht, ist isort manchmal noch flexibler, aber das wird immer weniger relevant.
|
||||
|
||||
---
|
||||
|
||||
### 3.5. Ruff vs. mypy/pyright (Typprüfung)
|
||||
|
||||
**mypy/pyright**:
|
||||
|
||||
- Statische Typprüfer: sie prüfen, ob die Typannotationen sinnvoll zusammenpassen.
|
||||
- Finden z. B. Fehler wie: „Funktion gibt laut Typ `str` zurück, tatsächlich aber `int`“.
|
||||
|
||||
**Ruff**:
|
||||
|
||||
- Enthält Regeln, die mit Typannotationen arbeiten (z. B. Style, Safety), aber **kein vollwertiger Typprüfer**.
|
||||
- Typfehler (im Sinne von mypy) sollten weiterhin mit mypy oder pyright geprüft werden.
|
||||
|
||||
---
|
||||
|
||||
### 3.6. Ruff vs. bandit (Security)
|
||||
|
||||
**bandit**:
|
||||
|
||||
- Spezialisiertes Security-Tool für Python.
|
||||
|
||||
**Ruff**:
|
||||
|
||||
- Hat viele bandit-Regeln integriert (Regelgruppe `S`).
|
||||
- Deckt gängige Sicherheitsfallen ab (z. B. `eval` auf untrusted Input, hartkodierte Passwörter etc.).
|
||||
- Für tiefgehende Security-Audits kann ein spezialisiertes Tool trotzdem sinnvoll sein.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst ruff?
|
||||
|
||||
### 4.1. Performance-Probleme in großen Projekten
|
||||
|
||||
Früher:
|
||||
|
||||
- flake8 + black + isort + bandit + mypy
|
||||
→ viele Tools, mehrfaches Einlesen des Codes, CI dauert lange.
|
||||
|
||||
Mit ruff:
|
||||
|
||||
- Ein Tool übernimmt Linting, Formatierung, Importe, einen großen Teil der Security-Regeln.
|
||||
- Deutlich weniger Laufzeit, besonders in CI-Pipelines oder bei großen Repositories.
|
||||
|
||||
---
|
||||
|
||||
### 4.2. Zu viele Tools, komplizierte Konfiguration
|
||||
|
||||
Problem:
|
||||
|
||||
- Unterschiedliche Konfigurationsdateien (`.flake8`, `pyproject.toml`, `setup.cfg`, `.isort.cfg`, `pyproject.toml` für black…)
|
||||
- Mehr Aufwand beim Onboarding neuer Teammitglieder.
|
||||
|
||||
Ruff:
|
||||
|
||||
- Typischerweise alles in **einer** Konfiguration (`pyproject.toml`).
|
||||
- Weniger bewegliche Teile, einfachere Wartung.
|
||||
|
||||
---
|
||||
|
||||
### 4.3. Inkonsequente Codequalität im Team
|
||||
|
||||
Ohne einheitliche Tools:
|
||||
|
||||
- Jede:r schreibt etwas anders.
|
||||
- Diskussionen in Code-Reviews drehen sich um Stil statt Inhalte.
|
||||
- Fehler (z. B. ungenutzte Variablen, potentielle Bugs) werden erst spät bemerkt.
|
||||
|
||||
Mit ruff:
|
||||
|
||||
- Gemeinsame, automatisierte Regeln.
|
||||
- Automatische Fixes für vieles (z. B. im Editor oder Pre-Commit-Hooks).
|
||||
- Code-Reviews können sich auf Architektur und Logik konzentrieren.
|
||||
|
||||
---
|
||||
|
||||
### 4.4. Technische Schulden reduzieren
|
||||
|
||||
In älteren Projekten:
|
||||
|
||||
- Viele kleine Stil- und Qualitätsprobleme haben sich angesammelt.
|
||||
- Niemand möchte „alles mal eben aufräumen“, weil Tools zu langsam sind oder es zu viel ist.
|
||||
|
||||
Mit ruff:
|
||||
|
||||
- Durch die Geschwindigkeit kann man auch große Codebasen lintern.
|
||||
- Schrittweise Verbesserung möglich: z. B. zunächst nur wichtige Regelgruppen aktivieren, später mehr.
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen bei der Nutzung von ruff
|
||||
|
||||
### 5.1. Zu viele Meldungen am Anfang
|
||||
|
||||
Wenn du ruff das erste Mal auf ein älteres Projekt loslässt, bekommst du oft hunderte oder tausende Meldungen.
|
||||
|
||||
Strategie:
|
||||
|
||||
- Nur einen Teil der Regeln aktivieren (z. B. nur „kritische“ oder klar hilfreiche).
|
||||
- Bestehende Verstöße einmalig ignorieren (per `--ignore` oder `--per-file-ignores`) und neue Verstöße blocken.
|
||||
- Nach und nach alte Stellen aufräumen.
|
||||
|
||||
---
|
||||
|
||||
### 5.2. Regeln verstehen und anpassen
|
||||
|
||||
Ruff hat sehr viele Regeln. Nicht alle passen zu jedem Projekt.
|
||||
|
||||
- Manche Regeln sind sehr streng (z. B. bestimmte Naming- oder Docstring-Regeln).
|
||||
- Du musst überlegen: Welche Regeln sind für unser Team sinnvoll?
|
||||
|
||||
Lösung:
|
||||
|
||||
- Regeln gezielt aktivieren/deaktivieren.
|
||||
- Dokumentieren, warum bestimmte Regeln aktiv oder abgeschaltet sind.
|
||||
|
||||
---
|
||||
|
||||
### 5.3. Wechsel von bestehender Tool-Landschaft
|
||||
|
||||
Wenn ihr schon flake8, black, isort etc. nutzt:
|
||||
|
||||
- Müssen Einstellungen in ruff nachgebaut werden.
|
||||
- Manche Teams werden black nicht sofort durch ruff format ersetzen wollen.
|
||||
|
||||
Ein pragmatischer Weg:
|
||||
|
||||
1. Zuerst ruff als **Linter** einführen (Formatierung bleibt bei black).
|
||||
2. Wenn gewünscht, später ruff format testen und ggf. black ersetzen.
|
||||
|
||||
---
|
||||
|
||||
### 5.4. Auto-Fixes mit Vorsicht genießen
|
||||
|
||||
Ruff kann sehr viel automatisch reparieren:
|
||||
|
||||
- In der Regel gut, aber:
|
||||
- Bei manchen Regeln sollte man prüfen, ob die Änderung wirklich die Absicht trifft.
|
||||
- In kritischen Bereichen (z. B. Security-sensible Logik) ggf. Auto-Fixes nicht blind akzeptieren.
|
||||
|
||||
Empfehlung:
|
||||
|
||||
- Auto-Fixes lokal ausführen, dann diff anschauen.
|
||||
- In CI eher nur prüfen, nicht fixen.
|
||||
|
||||
---
|
||||
|
||||
### 5.5. Editor-Integration
|
||||
|
||||
Ruff hat gute Unterstützung in vielen Editoren (VS Code, PyCharm, Neovim usw.), aber:
|
||||
|
||||
- Man muss oft ein Plugin oder eine Extension installieren.
|
||||
- Manchmal überschneidet sich das mit vorhandenen Tools (z. B. Black-Extension vs. Ruff-Formatter); das muss sauber konfiguriert werden.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele
|
||||
|
||||
### 6.1. Installation von ruff
|
||||
|
||||
Typisch über `pip`:
|
||||
|
||||
```bash
|
||||
pip install ruff
|
||||
```
|
||||
|
||||
Oder als dev-Abhängigkeit in `pyproject.toml` (Poetry/uv/pip-tools etc.).
|
||||
|
||||
---
|
||||
|
||||
### 6.2. Einfacher Aufruf
|
||||
|
||||
Linting:
|
||||
|
||||
```bash
|
||||
ruff check .
|
||||
```
|
||||
|
||||
Mit automatischen Fixes:
|
||||
|
||||
```bash
|
||||
ruff check . --fix
|
||||
```
|
||||
|
||||
Formatierung:
|
||||
|
||||
```bash
|
||||
ruff format .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.3. Minimale Konfiguration in `pyproject.toml`
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
target-version = "py311" # welche Python-Version ihr nutzt
|
||||
line-length = 88 # maximale Zeilenlänge
|
||||
exclude = ["venv", ".venv", "build", "dist"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "B", "UP"] # Regelgruppen: Errors, Flake, Imports, Bugbear, pyupgrade
|
||||
ignore = ["E501"] # Beispiel: lange Zeilen ignoriere ich (wenn ich black nutze)
|
||||
|
||||
[tool.ruff.format]
|
||||
# Beispiel: wenn ihr black-kompatible Einstellungen wollt
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
line-ending = "auto"
|
||||
```
|
||||
|
||||
- `select` gibt an, welche Regelgruppen aktiv sind.
|
||||
- `ignore` hebt einzelne Regelcodes auf (z. B. `E501` für Zeilenlänge).
|
||||
|
||||
---
|
||||
|
||||
### 6.4. Beispiel: Unbenutzte Variable und schlechtes `None`-Checking
|
||||
|
||||
Code:
|
||||
|
||||
```python
|
||||
def process(items):
|
||||
count = len(items)
|
||||
unused = 0
|
||||
|
||||
if items == None:
|
||||
print("No items")
|
||||
return count
|
||||
```
|
||||
|
||||
Ruff meldet u. a.:
|
||||
|
||||
- `F841` (unused variable `unused`)
|
||||
- `E711` (Vergleich mit `None` mit `==` statt `is`)
|
||||
|
||||
Mit `ruff check . --fix` könnte daraus werden:
|
||||
|
||||
```python
|
||||
def process(items):
|
||||
count = len(items)
|
||||
|
||||
if items is None:
|
||||
print("No items")
|
||||
return count
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.5. Beispiel: Imports aufräumen
|
||||
|
||||
Code:
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
|
||||
def get_data(url):
|
||||
r = requests.get(url)
|
||||
return r.json()
|
||||
```
|
||||
|
||||
Hier wird `os`, `sys` und `json` nicht genutzt.
|
||||
|
||||
Ruff meldet:
|
||||
|
||||
- `F401` (unused imports: os, sys, json)
|
||||
|
||||
Nach `ruff check . --fix`:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
|
||||
def get_data(url):
|
||||
r = requests.get(url)
|
||||
return r.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.6. Beispiel: Einsatz mit pre-commit
|
||||
|
||||
Viele Teams nutzen [pre-commit](https://pre-commit.com/), damit Checks automatisch vor jedem Git-Commit laufen.
|
||||
|
||||
`.pre-commit-config.yaml`:
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.6.5 # Beispielversion, ggf. aktualisieren
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
```
|
||||
|
||||
Damit werden:
|
||||
|
||||
- Linting + Auto-Fix vor jedem Commit ausgeführt,
|
||||
- Formatierung automatisch angewendet.
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusammenfassung
|
||||
|
||||
- **Ruff** ist ein modernes, sehr schnelles Tool zur Verbesserung der Code-Qualität in Python.
|
||||
- Es kombiniert:
|
||||
- Linting (Fehler und Stil),
|
||||
- Formatierung,
|
||||
- Import-Sortierung,
|
||||
- viele Sicherheits- und Modernisierungsregeln.
|
||||
- Es ersetzt in vielen Projekten:
|
||||
- flake8 (+ viele Plugins),
|
||||
- isort,
|
||||
- teilweise black,
|
||||
- teilweise bandit.
|
||||
- Es löst Probleme wie:
|
||||
- lange Tool-Laufzeiten,
|
||||
- fragmentierte Konfiguration,
|
||||
- inkonsistente Codequalität.
|
||||
- Herausforderungen sind v. a.:
|
||||
- Auswahl und Anpassung der Regeln,
|
||||
- Umstieg von bestehenden Tools,
|
||||
- Umgang mit großen Mengen an Anfangs-Warnungen.
|
||||
|
||||
Wenn du möchtest, kann ich dir im nächsten Schritt eine **konkrete Beispielkonfiguration für ein kleines oder ein großes Projekt** vorschlagen (inkl. welche Regelgruppen sinnvoll sind und welche man am Anfang lieber auslässt).
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
`uv` ist ein neues, sehr schnelles All-in-one-Tool für das Python-Ökosystem, entwickelt von Astral (den Machern von `ruff`).
|
||||
Es ist im Kern ein **extrem schneller Paket- und Environment-Manager**, der mehrere bekannte Tools ersetzt bzw. bündelt:
|
||||
|
||||
- `pip` / `pip-tools` (Installation & Lockfiles)
|
||||
- `virtualenv` / `venv` (virtuelle Umgebungen)
|
||||
- `pyenv` (verschiedene Python-Versionen verwalten)
|
||||
- Teile von `poetry` / `pipx` (Projekt- & Tool-Management)
|
||||
|
||||
`pip install uv` installiert dabei nur einen Bootstrapper, der das eigentliche `uv`-Binary nachlädt.
|
||||
|
||||
---
|
||||
|
||||
## Wofür ist `uv` gut?
|
||||
|
||||
### 1. Pakete extrem schnell installieren
|
||||
`uv` ist in Rust geschrieben und deutlich schneller als `pip` oder `pip-tools` beim Auflösen und Installieren von Abhängigkeiten.
|
||||
|
||||
Beispiel (Drop-in für pip):
|
||||
|
||||
```bash
|
||||
uv pip install requests
|
||||
```
|
||||
|
||||
Auch mit `requirements.txt`:
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Virtuelle Umgebungen verwalten
|
||||
|
||||
```bash
|
||||
# Neue venv erstellen
|
||||
uv venv .venv
|
||||
|
||||
# venv aktivieren (z. B. in bash)
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Python-Versionen verwalten
|
||||
|
||||
Ähnlich wie `pyenv`:
|
||||
|
||||
```bash
|
||||
# Bestimmte Python-Version installieren
|
||||
uv python install 3.12
|
||||
|
||||
# Projekt mit bestimmter Version starten
|
||||
uv run --python 3.12 main.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Projekte initialisieren & reproduzierbare Builds
|
||||
|
||||
`uv` unterstützt `pyproject.toml` und Lockfiles (`uv.lock`) für reproduzierbare Umgebungen:
|
||||
|
||||
```bash
|
||||
# Neues Projekt anlegen
|
||||
uv init my-project
|
||||
|
||||
cd my-project
|
||||
uv sync # Installiert Abhängigkeiten gemäß pyproject + uv.lock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Skripte/Tools direkt ausführen
|
||||
|
||||
Ähnlich wie `pipx`:
|
||||
|
||||
```bash
|
||||
uv run black .
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
`uv` kümmert sich im Hintergrund um die passende Umgebung und Abhängigkeiten.
|
||||
|
||||
---
|
||||
|
||||
## Kurzfassung
|
||||
|
||||
`uv` ist sinnvoll, wenn du:
|
||||
|
||||
- Python-Pakete **deutlich schneller** installieren willst,
|
||||
- **alles rund um Python-Umgebungen** (Packages, venvs, Python-Versionen, Tools) mit **einem** konsistenten Tool verwalten möchtest,
|
||||
- reproduzierbare Builds mit `pyproject.toml` und Lockfile brauchst,
|
||||
- eine moderne Alternative zu `pip`, `virtualenv`, `pyenv`, `pip-tools`, teilweise `poetry`/`pipx` suchst.
|
||||
|
||||
Wenn du aktuell `pip`, `virtualenv` & Co nutzt, kannst du mittelfristig sehr viel davon durch `uv` ersetzen.
|
||||
+469
@@ -0,0 +1,469 @@
|
||||
## 1. Grundsätzliche Definition: Was ist **uvicorn**?
|
||||
|
||||
**uvicorn** ist ein extrem performanter, asynchroner Web-Server für Python, der das **ASGI**‑Protokoll unterstützt.
|
||||
|
||||
- **ASGI** = *Asynchronous Server Gateway Interface*
|
||||
- uvicorn ist also das Bindeglied zwischen:
|
||||
- dem Web (HTTP, WebSockets)
|
||||
- und deiner Python‑Applikation (z.B. [[FastAPI]], Starlette, Django mit ASGI)
|
||||
|
||||
Uvicorn basiert intern auf sehr schnellen C‑Bibliotheken:
|
||||
- **uvloop** (schneller Event Loop, Ersatz für `asyncio`‑Loop)
|
||||
- **httptools** (schnelles HTTP‑Parsing)
|
||||
|
||||
Du verwendest uvicorn typischerweise, um eine ASGI‑App „zu starten“:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Wichtige Begriffe: ASGI, WSGI und Web-Frameworks
|
||||
|
||||
### 2.1 ASGI vs. WSGI
|
||||
|
||||
- **WSGI** (älterer Standard, z.B. für Django (klassisch), Flask):
|
||||
- synchron
|
||||
- kein natives WebSocket‑Support
|
||||
- typische Server: `gunicorn`, `uWSGI`, `mod_wsgi`
|
||||
|
||||
- **ASGI** (moderner Standard):
|
||||
- unterstützt **async/await**
|
||||
- kann **HTTP** und **WebSockets** und Background Tasks
|
||||
- typische Server: `uvicorn`, `hypercorn`, `daphne`
|
||||
|
||||
uvicorn ist also ein **ASGI-Server**, nicht WSGI.
|
||||
|
||||
### 2.2 uvicorn vs. Web-Frameworks ([[FastAPI]], Starlette, Django, Flask)
|
||||
|
||||
- **Framework** ([[FastAPI]], Starlette, Django, Flask):
|
||||
- definiert, wie du Routen, Views, Models, etc. schreibst.
|
||||
- kümmert sich um Request/Response‑Logik
|
||||
|
||||
- **uvicorn**:
|
||||
- kümmert sich um das Annehmen von Verbindungen, HTTP‑Parsing, Event‑Loop‑Handling.
|
||||
- ruft deine Applikation nur gemäß dem ASGI‑Protokoll auf.
|
||||
|
||||
Bildlich:
|
||||
**Browser** → (HTTP) → **uvicorn** → (ASGI) → **deine App** (z.B. [[FastAPI]])
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu ähnlichen oder verwandten Begriffen
|
||||
|
||||
### 3.1 uvicorn vs. Gunicorn
|
||||
|
||||
- **gunicorn**:
|
||||
- klassischer **WSGI**-Server (für sync‑Apps wie Flask oder Django ohne ASGI).
|
||||
- kann aber mithilfe von Workern wie `uvicorn.workers.UvicornWorker` auch ASGI-App starten.
|
||||
|
||||
Beispiel: [[FastAPI]]‑App mit gunicorn + uvicorn worker:
|
||||
|
||||
```bash
|
||||
gunicorn -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8000
|
||||
```
|
||||
|
||||
Hier ist:
|
||||
- gunicorn = Prozessmanager und Worker‑Spawner
|
||||
- uvicorn = eigentlicher ASGI‑Server pro Worker
|
||||
|
||||
### 3.2 uvicorn vs. Hypercorn / Daphne
|
||||
|
||||
- **hypercorn**:
|
||||
- anderer ASGI‑Server (unterstützt z.B. HTTP/2, verschiedene Event Loops)
|
||||
- **daphne**:
|
||||
- ASGI‑Server aus dem Django‑Channels‑Ökosystem
|
||||
|
||||
Alle drei (uvicorn, hypercorn, daphne) machen im Kern das Gleiche:
|
||||
**ASGI‑Apps ausführen**, unterscheiden sich aber in Features, Performance und Konfigurationsmöglichkeiten.
|
||||
|
||||
### 3.3 uvicorn vs. „eingebauter Development-Server“
|
||||
|
||||
Viele Frameworks haben eingebaute Dev-Server, z.B.:
|
||||
|
||||
- Flask: `app.run(debug=True)`
|
||||
- Django: `python manage.py runserver`
|
||||
|
||||
Diese sind:
|
||||
- für **Entwicklung** gedacht
|
||||
- nicht für **Produktion** (Performance, Stabilität, Security)
|
||||
|
||||
uvicorn ist ein **richtiger** Webserver, der für **Produktion** geeignet ist (oft zusammen mit einem Reverse Proxy wie [[Nginx]]).
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst uvicorn?
|
||||
|
||||
### 4.1 Asynchrone Web‑Backends performant betreiben
|
||||
|
||||
Mit ASGI kannst du:
|
||||
|
||||
- `async def` Endpoints schreiben
|
||||
- WebSockets nutzen
|
||||
- viele gleichzeitige Requests mit einem Event Loop bedienen
|
||||
|
||||
uvicorn ermöglicht dir, diese **asynchronen** Apps performant auszuliefern.
|
||||
|
||||
Praxisnahes Beispiel ([[FastAPI]]):
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from fastapi import FastAPI
|
||||
import asyncio
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int):
|
||||
await asyncio.sleep(1) # simuliert eine I/O-Operation
|
||||
return {"item_id": item_id}
|
||||
```
|
||||
|
||||
Starten mit uvicorn:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
uvicorn kümmert sich darum, dass mehrere Requests gleichzeitig abgearbeitet werden können, während `asyncio.sleep` nicht blockiert.
|
||||
|
||||
### 4.2 WebSockets und Long-Lived Connections
|
||||
|
||||
ASGI (und damit uvicorn) unterstützt **WebSockets** nativ, was mit WSGI nicht geht.
|
||||
|
||||
Beispiel mit Starlette:
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from starlette.applications import Starlette
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.websockets import WebSocket
|
||||
from starlette.routing import Route, WebSocketRoute
|
||||
|
||||
async def homepage(request):
|
||||
return JSONResponse({"hello": "world"})
|
||||
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
await websocket.send_text("Willkommen!")
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(f"Du hast gesendet: {data}")
|
||||
|
||||
routes = [
|
||||
Route("/", endpoint=homepage),
|
||||
WebSocketRoute("/ws", endpoint=websocket_endpoint),
|
||||
]
|
||||
|
||||
app = Starlette(routes=routes)
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
uvicorn main:app
|
||||
```
|
||||
|
||||
Mit WSGI wäre so ein WebSocket‑Endpoint nicht möglich.
|
||||
|
||||
### 4.3 Produktionstauglicher Server gegenüber Entwicklungsservern
|
||||
|
||||
- Stabilität bei hoher Last
|
||||
- Steuerung von:
|
||||
- Anzahl Worker‑Prozesse
|
||||
- Timeouts
|
||||
- Logging
|
||||
- Start via CLI, systemd, Docker, Kubernetes etc.
|
||||
|
||||
---
|
||||
|
||||
## 5. Grundlegende Verwendung von uvicorn
|
||||
|
||||
### 5.1 Installation
|
||||
|
||||
```bash
|
||||
pip install uvicorn
|
||||
# optional: schnellere Variante mit C-Extensions
|
||||
pip install "uvicorn[standard]"
|
||||
```
|
||||
|
||||
`[standard]` installiert u.a. `uvloop` und `httptools`.
|
||||
|
||||
### 5.2 Minimalbeispiel: Plain-ASGI-App
|
||||
|
||||
Du kannst eine ASGI‑App auch ohne Framework schreiben:
|
||||
|
||||
```python
|
||||
# app.py
|
||||
async def app(scope, receive, send):
|
||||
assert scope["type"] == "http"
|
||||
|
||||
# Request body lesen (vereinfachter Fall)
|
||||
await receive()
|
||||
|
||||
body = b"Hello, world"
|
||||
headers = [(b"content-type", b"text/plain")]
|
||||
|
||||
await send({
|
||||
"type": "http.response.start",
|
||||
"status": 200,
|
||||
"headers": headers,
|
||||
})
|
||||
await send({
|
||||
"type": "http.response.body",
|
||||
"body": body,
|
||||
})
|
||||
```
|
||||
|
||||
Starten:
|
||||
|
||||
```bash
|
||||
uvicorn app:app --reload
|
||||
```
|
||||
|
||||
Erklärung:
|
||||
- `app:app` = Modul `app.py`, Variable `app`
|
||||
- `--reload` = automatischer Neustart bei Codeänderung (nur dev)
|
||||
|
||||
### 5.3 Beispiel mit [[FastAPI]]
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Hello from uvicorn + FastAPI"}
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Wichtige CLI‑Optionen:
|
||||
- `--reload`: Auto-Reload bei Codeänderungen (Dev)
|
||||
- `--host`: z.B. `0.0.0.0` um von außen erreichbar zu sein
|
||||
- `--port`: Port, z.B. `8000`
|
||||
- `--workers`: Anzahl der Prozesse (für Produktion)
|
||||
|
||||
### 5.4 Starten aus Python heraus
|
||||
|
||||
```python
|
||||
# run.py
|
||||
import uvicorn
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
)
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Typische Konfigurationen und Szenarien
|
||||
|
||||
### 6.1 Entwicklung
|
||||
|
||||
- ein Worker
|
||||
- `--reload` aktiviert
|
||||
- Logging auf `debug`
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000 --log-level debug
|
||||
```
|
||||
|
||||
### 6.2 Produktion (einfach)
|
||||
|
||||
- mehrere Worker-Prozesse
|
||||
- kein `--reload`
|
||||
- Logging eher `info` oder `warning`
|
||||
|
||||
```bash
|
||||
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 --log-level info
|
||||
```
|
||||
|
||||
Richtwert für Worker:
|
||||
`Anzahl CPU-Kerne * 2` (abhängig von App und Last; immer testen).
|
||||
|
||||
### 6.3 Produktion hinter einem Reverse Proxy (z.B. [[Nginx]])
|
||||
|
||||
Typischer Aufbau:
|
||||
|
||||
```
|
||||
Internet → Nginx (TLS, gzip, etc.) → uvicorn → FastAPI/Starlette/Django
|
||||
```
|
||||
|
||||
- [[Nginx]] übernimmt TLS/SSL, Load Balancing, Static Files
|
||||
- uvicorn macht die Application-Logik
|
||||
|
||||
[[Nginx]]-Konfig (stark vereinfacht) könnte so aussehen:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
uvicorn wird weiterhin wie oben gestartet.
|
||||
|
||||
---
|
||||
|
||||
## 7. Herausforderungen und typische Stolpersteine
|
||||
|
||||
### 7.1 Async/Synchron-Mix und Blockierungen
|
||||
|
||||
**Problem:**
|
||||
Du verwendest uvicorn (ASGI, async), aber in deinen Endpoints gibt es blockierende Operationen:
|
||||
|
||||
- große CPU‑Aufgaben
|
||||
- synchrones Warten auf externe APIs (z.B. `requests.get(...)`)
|
||||
- schwere Datenbank‑Queries, die nicht async sind
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
@app.get("/slow")
|
||||
async def slow():
|
||||
import time
|
||||
time.sleep(5) # BLOCKIERT den Event-Loop
|
||||
return {"status": "ok"}
|
||||
```
|
||||
|
||||
Folge:
|
||||
- Ein Request blockiert den Event Loop → alle anderen Requests warten mit.
|
||||
|
||||
Lösungen:
|
||||
- I/O: async Libraries benutzen (z.B. `httpx` statt `requests`, `asyncpg` statt sync‑DB‑Client)
|
||||
- CPU‑lastig: in Thread‑Pool oder Process‑Pool auslagern (`run_in_threadpool` etc.)
|
||||
|
||||
### 7.2 Gemeinsamer Zustand über Worker-Prozesse
|
||||
|
||||
Wenn du `--workers > 1` nutzt, hast du **mehrere Prozesse**.
|
||||
Globaler Zustand in Python wird **nicht** zwischen Prozessen geteilt.
|
||||
|
||||
Beispiel (Problem):
|
||||
|
||||
```python
|
||||
counter = 0
|
||||
|
||||
@app.get("/count")
|
||||
def count():
|
||||
global counter
|
||||
counter += 1
|
||||
return {"counter": counter}
|
||||
```
|
||||
|
||||
Mit mehreren Workern:
|
||||
- jeder Worker hat seinen eigenen `counter`
|
||||
- Ergebnisse sind inkonsistent
|
||||
|
||||
Lösung:
|
||||
- geteilten Zustand über externe Systeme (Redis, Datenbank, etc.)
|
||||
- oder nur einen Worker nutzen, wenn globaler In-Memory-State unvermeidbar ist (aber meist unsauber).
|
||||
|
||||
### 7.3 Datenbankverbindungen und Lebenszyklus
|
||||
|
||||
uvicorn unterstützt ASGI‑`lifespan`‑Events (startup/shutdown).
|
||||
Frameworks wie [[FastAPI]]/Starlette nutzen das, um z.B. DB‑Connections zu öffnen/schließen.
|
||||
|
||||
Stolpersteine:
|
||||
- Verbindungspools pro Worker korrekt initialisieren
|
||||
- bei Shutdown sauber schließen
|
||||
- nicht „pro Request“ neue Connections aufmachen
|
||||
|
||||
Beispiel mit [[FastAPI]] (vereinfacht):
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
db = None
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
global db
|
||||
db = await some_async_db_connect()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown():
|
||||
await db.close()
|
||||
```
|
||||
|
||||
### 7.4 Logging und Error-Handling
|
||||
|
||||
uvicorn hat eigenes Logging; dein Framework ebenso.
|
||||
Typische Themen:
|
||||
|
||||
- Log-Format in Produktion standardisieren
|
||||
- Fehler-Logs im Zusammenspiel mit Reverse Proxy
|
||||
- Ausführliche Logs in Dev, weniger in Prod
|
||||
|
||||
Beispiel (JSON-Logging in Produktion, nur angedeutet):
|
||||
|
||||
```bash
|
||||
uvicorn main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8000 \
|
||||
--log-config logging_config.yaml
|
||||
```
|
||||
|
||||
In `logging_config.yaml` kannst du detailliert das Logging steuern.
|
||||
|
||||
### 7.5 Plattformunterschiede (Windows vs. Linux)
|
||||
|
||||
- `--reload` nutzt File-Watcher und Signale → unter Linux sehr stabil; unter Windows kann es ein paar Besonderheiten geben.
|
||||
- In Produktion läuft uvicorn meist auf Linux‑Servern oder in Docker‑Containern.
|
||||
|
||||
---
|
||||
|
||||
## 8. Kurze Checkliste für den praktischen Einstieg
|
||||
|
||||
1. **Framework wählen**
|
||||
- [[FastAPI]] oder Starlette, wenn du intensiv async nutzen willst.
|
||||
1. **App schreiben**
|
||||
- `app = FastAPI()`, Endpoints definieren.
|
||||
3. **In Entwicklung starten**
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
4. **Vor Produktion**
|
||||
- Blocking‑Code prüfen (CPU, I/O)
|
||||
- Datenbankzugriff sauber konfigurieren (Pools, async‑Client)
|
||||
- Logging und Error‑Handling aufräumen
|
||||
5. **In Produktion starten** (einfach)
|
||||
```bash
|
||||
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
```
|
||||
6. **Optional**:
|
||||
- vor uvicorn einen [[Nginx]] oder Traefik setzen (TLS, Load Balancing)
|
||||
|
||||
---
|
||||
|
||||
## 9. Zusammenfassung
|
||||
|
||||
- **uvicorn** ist ein **ASGI‑Webserver** für Python, optimiert für **asynchrone** Web‑Apps.
|
||||
- Er ist **kein Web‑Framework**, sondern die Laufzeitumgebung für Frameworks wie **[[FastAPI]]**, **Starlette** oder moderne **Django**‑Konfigurationen.
|
||||
- Es löst die Probleme klassischer WSGI‑Server in Bezug auf **Async**, **WebSockets** und **Performance**.
|
||||
- Typische Herausforderungen liegen im Bereich:
|
||||
- korrektes Async‑Design
|
||||
- Umgang mit mehreren Workern und gemeinsamem Zustand
|
||||
- saubere Integration von Datenbanken, Logging, Deployment
|
||||
- Für dich als Python‑Entwickler ist uvicorn im Alltag vor allem:
|
||||
**das Kommando, mit dem du deine moderne Web‑API startest.**
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
Hier ist eine umfassende Einführung in Argo CD – verständlich, aber fachlich fundiert.
|
||||
|
||||
---
|
||||
|
||||
## 1. Was ist Argo CD?
|
||||
|
||||
Argo CD ist ein **GitOps-Tool** für **Kubernetes**. Es sorgt dafür, dass der Zustand eines Kubernetes-Clusters immer mit dem übereinstimmt, was in einem **Git-Repository** als „Wahrheit“ definiert ist.
|
||||
|
||||
Kerneigenschaften:
|
||||
|
||||
- **Git-zentriert**: Git als Single Source of Truth (Manifest-Dateien, Helm-Charts, Kustomize, etc.)
|
||||
- **Deklarativ**: Der gewünschte Zustand (desired state) ist beschrieben, nicht programmiert
|
||||
- **Kontinuierliche Synchronisierung**: Änderungen in Git werden automatisch oder manuell im Cluster ausgerollt
|
||||
- **Visuell**: Web UI mit Übersicht über Applikationen, Health-Status, Diffs, History
|
||||
- **Mehrcluster-fähig**: Kann mehrere Kubernetes-Cluster verwalten
|
||||
|
||||
---
|
||||
|
||||
## 2. GitOps – der Kontext von Argo CD
|
||||
|
||||
Argo CD implementiert das GitOps-Konzept:
|
||||
|
||||
- **Infrastruktur & Deployments im Git** (Manifest-Dateien, Helm Values, Kustomize Overlays, etc.)
|
||||
- **Pull-basiert**: Argo CD „zieht“ den Zustand regelmäßig aus Git und gleicht ihn mit dem Cluster ab.
|
||||
- **Versioniert & auditiert**: Jede Änderung ist ein Git-Commit, inkl. History, Review, Rollback-Möglichkeit
|
||||
- **Automatisierte Drift-Erkennung**: Unterschiede zwischen Git und Cluster werden erkannt und gemeldet
|
||||
|
||||
Vorteile von GitOps mit Argo CD:
|
||||
|
||||
- Reproduzierbare Deployments
|
||||
- Besseres Change-Management (Git-Workflows, PRs)
|
||||
- Weniger manuelle „[[kubectl]] apply“-Befehle
|
||||
- Klare Trennung von Build (CI) und Deployment (CD)
|
||||
|
||||
---
|
||||
|
||||
## 3. Architektur von Argo CD
|
||||
|
||||
### 3.1 Hauptkomponenten
|
||||
|
||||
- **argocd-server**
|
||||
- Stellt die Web UI und das gRPC/REST-API bereit
|
||||
- Authentifizierung (lokale User, SSO via OIDC, etc.)
|
||||
- Kommunikation mit CLI (`argocd` CLI) und UI
|
||||
|
||||
- **argocd-repo-server**
|
||||
- Greift auf Git-Repositories zu
|
||||
- Rendert Manifeste (z.B. Helm, Kustomize, Jsonnet)
|
||||
- Führt Template-Engines und „Config Management Plugins“ aus
|
||||
|
||||
- **argocd-application-controller**
|
||||
- Herzstück des Sync-Mechanismus
|
||||
- Vergleicht desired state (Git) mit live state (Cluster)
|
||||
- Führt Syncs, Health-Checks und Rollbacks aus
|
||||
|
||||
- **argocd-redis** (optional, in vielen Installationen vorhanden)
|
||||
- Cache für Repositories und Application-Informationen
|
||||
|
||||
Diese Komponenten laufen typischerweise im Namespace `argocd` im Cluster.
|
||||
|
||||
### 3.2 Target Cluster
|
||||
|
||||
Argo CD kann:
|
||||
|
||||
- Den **Cluster, in dem es selbst läuft**, verwalten
|
||||
- **Externe Cluster** (z.B. Staging, Prod) als zusätzliche „Cluster Secrets“ anbinden
|
||||
|
||||
---
|
||||
|
||||
## 4. Zentrale Konzepte
|
||||
|
||||
### 4.1 Application
|
||||
|
||||
Die zentrale Ressource in Argo CD ist eine CRD namens **Application** (`kind: Application`).
|
||||
|
||||
Eine Application beschreibt:
|
||||
|
||||
- **Quelle (source)**: Woher kommen die Manifeste?
|
||||
- Git-Repository / Helm-Repo / Kustomize-Ordner
|
||||
- Pfad im Repo
|
||||
- ggf. Helm-Chart und Values
|
||||
- **Ziel (destination)**:
|
||||
- Zu verwaltender Kubernetes-Cluster
|
||||
- Ziel-Namespace
|
||||
- **Sync-Strategie & -Optionen**:
|
||||
- Automatische Synchronisierung (auto sync) oder manuell
|
||||
- Verhalten bei Fehlern, Prune, Self-Heal etc.
|
||||
|
||||
Ein minimaler `Application`-Beispiel (vereinfachtes YAML):
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: my-app
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/mein-org/mein-repo.git
|
||||
targetRevision: main
|
||||
path: kubernetes/my-app
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: my-app
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
```
|
||||
|
||||
### 4.2 Sync-Status & Health-Status
|
||||
|
||||
Für jede Application zeigt Argo CD zwei wesentliche Zustände:
|
||||
|
||||
- **Sync-Status**:
|
||||
- `Synced`: Cluster-Zustand entspricht Git-Zustand
|
||||
- `OutOfSync`: Unterschiede zwischen Git und Cluster
|
||||
- **Health-Status**:
|
||||
- `Healthy`: Anwendung läuft wie erwartet (z.B. alle Pods bereit)
|
||||
- `Degraded`: Fehlerzustände (CrashLoop, fehlende Ressourcen etc.)
|
||||
- `Progressing`: Deployment läuft noch, noch nicht stabil
|
||||
|
||||
Health-Checks sind ressourcentyp-spezifisch (Deployments, StatefulSets, CRDs etc.).
|
||||
|
||||
### 4.3 Drift-Erkennung
|
||||
|
||||
Argo CD vergleicht:
|
||||
|
||||
- **Desired state**: Manifeste, die aus Git gerendert wurden
|
||||
- **Live state**: Tatsächliche Ressourcen im Cluster
|
||||
|
||||
Wenn jemand manuell `kubectl apply` oder `kubectl edit` verwendet, entsteht „Drift“. Argo CD:
|
||||
|
||||
- Markiert den Status als `OutOfSync`
|
||||
- Kann bei `selfHeal: true` diese Änderungen automatisch wieder rückgängig machen (zurück auf Git-Zustand)
|
||||
|
||||
---
|
||||
|
||||
## 5. Unterstützte Werkzeuge & Formate
|
||||
|
||||
Argo CD unterstützt u.a.:
|
||||
|
||||
- **Plain YAML-Manifeste**
|
||||
- **Helm** (Charts, Values)
|
||||
- **Kustomize**
|
||||
- **Jsonnet**
|
||||
- **Ksonnet (legacy)**
|
||||
|
||||
Außerdem:
|
||||
|
||||
- **Config Management Plugins (CMP)**: Eigene Tools/Pipelines zum Manifest-Rendering integrierbar
|
||||
- **Multiple Sources**: Neuere Versionen unterstützen mehrere Quellen pro Application (z.B. App-Manifeste + separate Helm-Values)
|
||||
|
||||
---
|
||||
|
||||
## 6. Installation & erste Schritte
|
||||
|
||||
### 6.1 Installation
|
||||
|
||||
Typische Wege:
|
||||
|
||||
- **kubectl apply** eines offiziellen Install-Manifests:
|
||||
- z.B. `https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml`
|
||||
- **Helm-Chart** (empfohlen für produktivere Setups)
|
||||
- **Operator-Lösungen** in manchen Distributionen
|
||||
|
||||
Nach der Installation:
|
||||
|
||||
- Argo CD läuft im Namespace `argocd`
|
||||
- UI via `argocd-server` (LoadBalancer, Ingress oder Port-Forward)
|
||||
- Erstlogin meist über das Passwort aus dem `argocd-initial-admin-secret`
|
||||
|
||||
### 6.2 CLI
|
||||
|
||||
Das CLI-Tool `argocd` erlaubt:
|
||||
|
||||
- Login: `argocd login <server>`
|
||||
- Erstellung von Applications: `argocd app create ...`
|
||||
- Manuelles Syncen: `argocd app sync my-app`
|
||||
- Status anzeigen, Diffs, Rollbacks etc.
|
||||
|
||||
---
|
||||
|
||||
## 7. Deployment-Strategien & Sync-Policies
|
||||
|
||||
### 7.1 Manuelles vs. automatisches Syncen
|
||||
|
||||
- **Manuell**:
|
||||
- Änderungen in Git werden erkannt, aber nur als `OutOfSync` markiert
|
||||
- Deploy erfolgt erst nach `argocd app sync` (CLI/UI)
|
||||
- **Automatisch (auto sync)**:
|
||||
- Argo CD führt bei Änderungen in Git automatisch ein Sync aus
|
||||
- Option `prune` entfernt Ressourcen, die in Git gelöscht wurden
|
||||
- Option `selfHeal` korrigiert Drift im Cluster
|
||||
|
||||
### 7.2 Sync-Optionen
|
||||
|
||||
- **Sync-Wellen (Sync Waves)**:
|
||||
- Steuerung der Reihenfolge via `argocd.argoproj.io/sync-wave` Annotation
|
||||
- **Sync-Hooks**:
|
||||
- Vor-/Nachschaltbare Hooks (`PreSync`, `PostSync`, `SyncFail` etc.)
|
||||
- Realisiert mit bestimmten Kubernetes-Resourcen und Annotationen
|
||||
|
||||
---
|
||||
|
||||
## 8. Projekte (AppProjects) & Multi-Tenancy
|
||||
|
||||
Die CRD **AppProject** erlaubt die logische Gruppierung und Begrenzung von Applications.
|
||||
|
||||
Funktionen von AppProjects:
|
||||
|
||||
- Einschränkung von:
|
||||
- Erlaubten Git-Repositories
|
||||
- Erlaubten Ziel-Cluster / Namespaces
|
||||
- Erlaubten Ressourcentypen
|
||||
- Gemeinsame Policies & RBAC für eine Gruppe von Apps
|
||||
- Sinnvoll für **Multi-Tenancy**:
|
||||
- z.B. ein Project pro Team, Produkt oder Mandant
|
||||
|
||||
---
|
||||
|
||||
## 9. Sicherheit & RBAC
|
||||
|
||||
### 9.1 Authentifizierung
|
||||
|
||||
- Lokale Benutzer in Argo CD (Admin, weitere User)
|
||||
- Single Sign-On via:
|
||||
- OIDC (z.B. Keycloak, Dex, Azure AD, Okta)
|
||||
- SAML (über OIDC-Integrationen)
|
||||
- Tokens für Automatisierung (CI-Systeme, Bots)
|
||||
|
||||
### 9.2 Autorisierung (RBAC)
|
||||
|
||||
Argo CD hat ein eigenes RBAC-System:
|
||||
|
||||
- Rollen (z.B. `role:readonly`, `role:admin`, benutzerdefinierte Rollen)
|
||||
- Zuordnung von Rollen zu Subjekten (User, Gruppen)
|
||||
- Rechte auf:
|
||||
- Anwendungen (lesen, syncen, löschen)
|
||||
- Projekte
|
||||
- Cluster, Repos
|
||||
- Granulare Steuerung:
|
||||
- z.B. „Team A darf nur Apps in Project X syncen“
|
||||
|
||||
---
|
||||
|
||||
## 10. Typische Patterns: App-of-Apps & Monorepo/Multirepo
|
||||
|
||||
### 10.1 App-of-Apps Pattern
|
||||
|
||||
Ein häufiges Muster ist die **„Application of Applications“**:
|
||||
|
||||
- Eine „Root-Application“, deren Manifeste nur aus anderen `Application`-Ressourcen bestehen
|
||||
- Diese Root-App definiert alle anderen Applikationen/Umgebungen
|
||||
- Vorteile:
|
||||
- Zentrales Entry-Point
|
||||
- Environments (dev/stage/prod) als Under-Apps
|
||||
- Bessere Struktur bei vielen Services
|
||||
|
||||
### 10.2 Repository-Strukturen
|
||||
|
||||
- **Monorepo**:
|
||||
- Alle Manifeste aller Services und Environments in einem Git-Repo
|
||||
- **Multirepo**:
|
||||
- Pro Service oder Team ein eigenes Repo
|
||||
- Infrastruktur/Plattform in separaten Repos
|
||||
- Argo CD unterstützt beide Modelle – Entscheidung ist organisatorisch/architektonisch.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integration in CI/CD-Pipelines
|
||||
|
||||
Argo CD übernimmt in einem klassischen Setup die **CD-Rolle**:
|
||||
|
||||
- CI-Pipeline (z.B. GitLab CI, GitHub Actions, Jenkins):
|
||||
- Buildet Container-Images
|
||||
- Pusht Images in ein Registry
|
||||
- Aktualisiert Versionsnummern/Tags in den Git-Manifesten (Deployment-Repo)
|
||||
- Argo CD:
|
||||
- Erkennt Änderungen im Git-Repo
|
||||
- Deployt diese in den Cluster
|
||||
|
||||
Dadurch entsteht eine klare Trennung:
|
||||
|
||||
- CI = Builds, Tests, Image-Erzeugung
|
||||
- CD = Versionswechsel im Git + Argo CD Sync
|
||||
|
||||
---
|
||||
|
||||
## 12. Monitoring & Observability
|
||||
|
||||
- Argo CD bietet:
|
||||
- Events & Logs (Application-Controller, Repo-Server etc.)
|
||||
- Metriken (Prometheus-Exporter) für:
|
||||
- Anzahl Apps, Sync-Status, Health
|
||||
- Dauer von Syncs
|
||||
- Fehler, Retries
|
||||
- Integration mit:
|
||||
- **Prometheus/Grafana** (Dashboards für Argo CD)
|
||||
- Alerting auf Drift, Degradation, Sync-Fehler
|
||||
|
||||
---
|
||||
|
||||
## 13. Best Practices
|
||||
|
||||
Einige bewährte Vorgehensweisen:
|
||||
|
||||
1. **Git als einzige Wahrheit**
|
||||
- Keine manuellen Änderungen via `kubectl` in produktiven Namespaces
|
||||
2. **Automatisches Syncen mit Self-Heal** (mindestens in lower Environments)
|
||||
3. **Trennung von Image-Build-Repo und Deployment-Repo**
|
||||
4. **AppProjects pro Team/Domain** zur Strukturierung und Begrenzung
|
||||
5. **RBAC sauber definieren**:
|
||||
- Admin-Rechte nur für wenige
|
||||
- Team-spezifische Rollen
|
||||
6. **Health-Konfigurationen pflegen**:
|
||||
- Custom Health Checks für eigene CRDs, falls nötig
|
||||
7. **Klare Namenskonventionen** für Applications, Projekte und Repos
|
||||
8. **App-of-Apps** für große Landschaften:
|
||||
- z.B. `cluster-bootstrap` Application, die alle anderen Apps initialisiert
|
||||
|
||||
---
|
||||
|
||||
## 14. Typische Fallstricke
|
||||
|
||||
- **Drift durch manuelle Änderungen**:
|
||||
- Ursache für OutOfSync und schwer zu reproduzierende Zustände
|
||||
- **Fehlerhafte oder komplexe Helm-/Kustomize-Setups**:
|
||||
- Render-Probleme im Repo-Server bei sehr komplexen Konfigurationen
|
||||
- **Unzureichende Rechte im Ziel-Cluster**:
|
||||
- Argo CD-Service-Account braucht passende RBAC-Rechte
|
||||
- **Netzwerk-/Firewall-Probleme**:
|
||||
- Argo CD muss das Git-Repo erreichen können (Proxy, SSL, SSH-Keys)
|
||||
- **Stateful-Migrationen**:
|
||||
- Vorsicht bei Datenbank-Migrationen im Sync-Prozess (Hook-Jobs, Migrationsphasen)
|
||||
|
||||
---
|
||||
|
||||
## 15. Argo CD vs. andere GitOps-Tools (z.B. Flux)
|
||||
|
||||
Kurzvergleich zu **Flux** (ein anderes populäres GitOps-Tool):
|
||||
|
||||
- **Argo CD**:
|
||||
- Sehr starke, moderne **UI**
|
||||
- Klare Application-Entität
|
||||
- Häufig intuitiv für Teams, die visuelle Steuerung wünschen
|
||||
- **Flux**:
|
||||
- Sehr „Kubernetes-native“, keine eigene zentrale UI (oft mit zusätzlicher UIs wie Weave GitOps)
|
||||
- Stärker über CRDs und CLI gesteuert, weniger „zentrales“ Dashboard
|
||||
|
||||
Beide implementieren GitOps; die Wahl hängt oft von:
|
||||
|
||||
- UI-Bedarf
|
||||
- Präferenzen des Teams
|
||||
- Vorhandenen Ökosystem-Komponenten
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
Im IT-Kontext bedeutet **Ingress** allgemein:
|
||||
> „Eingehender Datenverkehr in ein System, Netzwerk oder einen Dienst.“
|
||||
|
||||
Je nach Kontext wird der Begriff etwas unterschiedlich verwendet:
|
||||
# Allgemein
|
||||
|
||||
### 1. Netzwerk / Security
|
||||
|
||||
In klassischen Netzwerken und Firewalls unterscheidet man:
|
||||
|
||||
- **Ingress-Traffic**: Datenverkehr, der **von außen nach innen** in ein Netzwerk oder System hineinkommt
|
||||
(z. B. HTTP-Anfragen aus dem Internet an Ihren Webserver).
|
||||
- **Egress-Traffic**: Datenverkehr, der **von innen nach außen** geht
|
||||
(z. B. Ihr Server ruft eine externe API im Internet auf).
|
||||
|
||||
Typische Themen rund um Ingress in diesem Kontext:
|
||||
- **Ingress-Filterung**: Regeln, die festlegen, welcher eingehende Traffic erlaubt ist (z. B. Firewall-Regeln).
|
||||
- **Ingress-Schutz**: Maßnahmen gegen Angriffe von außen (DDoS-Schutz, WAF, Rate Limiting).
|
||||
|
||||
### 2. Kubernetes (Spezialfall „Ingress“ Resource)
|
||||
|
||||
In Kubernetes ist **Ingress** ein eigener Begriff und bezeichnet eine **API-Ressource**, die regelt, wie externe Requests in den Cluster hinein zu den internen Services geleitet werden.
|
||||
|
||||
Kurz gesagt:
|
||||
- Ein **Ingress** ist eine Art **Reverse Proxy / Routing-Regel** auf Layer 7 (HTTP/HTTPS).
|
||||
- Er definiert z. B.:
|
||||
- Welche Domain (`host`) auf welchen Service zeigt
|
||||
- Welche Pfade (`/api`, `/app`) zu welchen Services geroutet werden
|
||||
- SSL/TLS-Termination (HTTPS)
|
||||
|
||||
Beispielhafte Ingress-Definition in Kubernetes:
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: my-app-ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: myapp.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: my-api-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Hier sorgt der Ingress dafür, dass Anfragen an `https://myapp.example.com/api` in den Cluster gehen und beim Service `my-api-service` landen.
|
||||
|
||||
---
|
||||
|
||||
Im Kubernetes-Kontext ist „Ingress“ etwas spezieller als nur „eingehender Traffic“. Es gibt drei eng zusammenhängende Dinge:
|
||||
|
||||
1. **Ingress-Ressource** (YAML-Objekt, das Routing-Regeln beschreibt)
|
||||
2. **Ingress-Controller** (Software/Pod, der diese Regeln umsetzt)
|
||||
3. **(Optional) IngressClass** (welcher Controller für welches Ingress zuständig ist)
|
||||
|
||||
Ich gehe der Reihe nach durch.
|
||||
|
||||
---
|
||||
# Kubernetes
|
||||
## 1. Warum braucht man Ingress überhaupt?
|
||||
|
||||
Ohne Ingress hast du im Wesentlichen drei Möglichkeiten, Dienste nach außen verfügbar zu machen:
|
||||
|
||||
- `ClusterIP` (Standard): nur intern im Cluster erreichbar
|
||||
- `NodePort`: öffnet einen Port auf jedem Node; eher low-level / unkomfortabel
|
||||
- `LoadBalancer`: Cloud-Loadbalancer vor einem Service (oft pro Service ein eigener LB)
|
||||
|
||||
Nachteile:
|
||||
- Viele externe IPs / Loadbalancer, wenn du viele Services hast
|
||||
- Keine zentrale Layer‑7-Logik (z. B. Routing per Hostname/URL-Pfad, TLS-Termination)
|
||||
|
||||
**Ingress** löst genau das:
|
||||
|
||||
> Ein (oder wenige) externe Einstiegspunkte, die per Hostname und Pfad auf viele interne Services routen können, inkl. HTTPS/TLS, Auth, Rate-Limiting (je nach Controller).
|
||||
|
||||
---
|
||||
|
||||
## 2. Ingress-Ressource (das YAML-Objekt)
|
||||
|
||||
Die Ingress-Ressource beschreibt **Deklarativ**, wie Anfragen von außen zu Services im Cluster geroutet werden sollen:
|
||||
|
||||
- Auf Basis von **Hostname** (`host`, z. B. `api.example.com`)
|
||||
- Auf Basis von **Pfad** (`path`, z. B. `/api`, `/app`)
|
||||
- Verknüpft mit einem **Backend-Service** (`Service` + Port)
|
||||
- Optional: **TLS**-Konfiguration (Zertifikate)
|
||||
|
||||
Beispiel:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: my-app-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- myapp.example.com
|
||||
secretName: myapp-tls-secret
|
||||
rules:
|
||||
- host: myapp.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: my-api-service
|
||||
port:
|
||||
number: 80
|
||||
- path: /app
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: my-frontend-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Was hier passiert:
|
||||
|
||||
- Alle Anfragen an `https://myapp.example.com/api...` → Service `my-api-service`
|
||||
- Alle Anfragen an `https://myapp.example.com/app...` → Service `my-frontend-service`
|
||||
- TLS-Zertifikat liegt in `Secret myapp-tls-secret`
|
||||
- `ingressClassName: nginx` sagt: „Dieser Ingress ist für den Ingress-Controller mit Klasse `nginx` gedacht“
|
||||
|
||||
Wichtige Felder:
|
||||
|
||||
- `rules[].host`: auf welchen Host gilt die Regel?
|
||||
- `rules[].http.paths[].path`: URL-Pfad
|
||||
- `pathType`:
|
||||
- `Prefix` → „beginnt mit diesem Pfad“
|
||||
- `Exact` → „genau dieser Pfad“
|
||||
- (früher gab es noch `ImplementationSpecific`)
|
||||
- `backend.service.name` / `port`: zu welchem Service leiten?
|
||||
|
||||
---
|
||||
|
||||
## 3. Ingress-Controller (die „Engine“ dahinter)
|
||||
|
||||
Die YAML-Ressource alleine macht noch nichts. Es braucht einen **Ingress-Controller**:
|
||||
|
||||
- Läuft als Pod/Deployment im Cluster
|
||||
- Beobachtet (watch) Ingress-Ressourcen im API-Server
|
||||
- Übersetzt die Regeln in eine konkrete Konfiguration (z. B. [[NGINX]], Envoy, Traefik …)
|
||||
- Lauscht auf einem (oder mehreren) Node-Ports/LoadBalancer-IPs und routet Traffic in die passende Services
|
||||
|
||||
Typische Implementierungen:
|
||||
|
||||
- **[[NGINX]] Ingress Controller**
|
||||
- **Traefik**
|
||||
- **HAProxy Ingress**
|
||||
- **Envoy-basierte** Controller
|
||||
- Cloud-spezifische:
|
||||
- GCE/GLBC (GKE HTTP(S) Load Balancing)
|
||||
- AWS ALB Ingress Controller
|
||||
- Azure Application Gateway Ingress
|
||||
|
||||
Wichtig:
|
||||
Du kannst mehrere Ingress-Controller im Cluster haben, z. B. einen für öffentliche, einen für interne Services.
|
||||
|
||||
---
|
||||
|
||||
## 4. IngressClass (Zuordnung Controller ↔ Ingress)
|
||||
|
||||
Mit `IngressClass` kann man genau definieren, **welcher** Ingress-Controller für welche Ingress-Ressourcen zuständig ist.
|
||||
|
||||
Beispiel IngressClass:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
controller: k8s.io/ingress-nginx
|
||||
```
|
||||
|
||||
Im Ingress verknüpfst du das mit:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
# ...
|
||||
```
|
||||
|
||||
Somit:
|
||||
|
||||
- Nur der Ingress-Controller, der sich als `k8s.io/ingress-nginx` registriert, reagiert auf diesen Ingress.
|
||||
- Andere Controller ignorieren ihn.
|
||||
|
||||
Früher wurde das oft über **Annotations** geregelt (`kubernetes.io/ingress.class`), `ingressClassName` ist der neuere, bevorzugte Weg.
|
||||
|
||||
---
|
||||
|
||||
## 5. Request-Flow: Wie läuft eine Anfrage technisch?
|
||||
|
||||
Typischer Ablauf (z. B. mit [[NGINX]] Ingress Controller in einer Cloud):
|
||||
|
||||
1. Benutzer ruft im Browser `https://myapp.example.com/api/users` auf.
|
||||
2. DNS zeigt `myapp.example.com` auf die externe IP eines Cloud-Loadbalancers.
|
||||
3. Der Cloud-Loadbalancer leitet die Anfrage an einen Node/Port, auf dem der Ingress-Controller erreichbar ist.
|
||||
4. Der Ingress-Controller (Pod) erhält die HTTP(S)-Anfrage.
|
||||
5. Er vergleicht Host (`myapp.example.com`) und Pfad (`/api/users`) mit seinen Ingress-Regeln:
|
||||
- Host passt zu `myapp.example.com`
|
||||
- Pfad passt zum Prefix `/api`
|
||||
6. Er leitet die Anfrage intern an den Kubernetes-Service `my-api-service:80` weiter.
|
||||
7. Kubernetes-Service verteilt die Anfrage per kube-proxy/IPVS/iptables auf einen Pod (`my-api`-Pod).
|
||||
8. Antwort geht den Weg zurück und landet beim Benutzer.
|
||||
|
||||
---
|
||||
|
||||
## 6. Typische Features (Controller-abhängig)
|
||||
|
||||
Je nach Ingress-Controller gibt es zusätzliche Möglichkeiten, meist über **Annotations** oder spezifische CRDs:
|
||||
|
||||
- **TLS-Termination** und optional TLS-Passthrough
|
||||
- **HTTP → HTTPS Redirect**
|
||||
- **Path-Rewrites**, z. B. `/api` außen → `/` im Backend
|
||||
- **Authentifizierung** (z. B. OAuth2, Basic Auth)
|
||||
- **Rate Limiting**, IP-Whitelist/Blacklist
|
||||
- **Canary-Deployments / Traffic-Splitting**
|
||||
- **gRPC-Support**, WebSockets, etc.
|
||||
|
||||
Beispiel (nginx Spezifische Annotation für Redirect):
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Grenzen von Ingress & Gateway API
|
||||
|
||||
Die klassische Ingress-API ist bewusst relativ simpel (Host, Pfad, Backend, TLS).
|
||||
Für komplexere Szenarien (multi-tenant, umfangreiche Routing-Logik, mehrere Gateways) gibt es inzwischen die **Gateway API** (Ressourcen wie `Gateway`, `HTTPRoute` etc.), die Ingress langfristig ergänzen/teilweise ablösen soll.
|
||||
|
||||
Dennoch ist Ingress aktuell (2024) in vielen Clustern der Standardweg, HTTP/HTTPS-Traffic von außen hereinzubringen.
|
||||
|
||||
---
|
||||
|
||||
Wenn du möchtest, kann ich als Nächstes:
|
||||
|
||||
- ein konkretes Setup „von Null an“ durchgehen (z. B. Minikube + [[NGINX]] Ingress + Beispiel-App), oder
|
||||
- spezifisch auf Annotations/Features eines bestimmten Ingress-Controllers eingehen (etwa nginx oder Traefik).
|
||||
@@ -0,0 +1,427 @@
|
||||
Ein JSON Web Token (JWT) wirkt am Anfang oft etwas „magisch“. Wenn man es einmal verstanden hat, ist es aber ein relativ simples Baustein-konzept für Authentifizierung und Autorisierung in modernen Web‑ und API‑Systemen.
|
||||
|
||||
Ich gehe Schritt für Schritt durch:
|
||||
|
||||
1. Grundidee: Was ist ein JWT?
|
||||
2. Aufbau eines JWT
|
||||
3. Abgrenzung zu verwandten Konzepten
|
||||
4. Welche Probleme werden mit JWT gelöst?
|
||||
5. Typische Anwendungsfälle (praxisnah erklärt)
|
||||
6. Herausforderungen und Fallstricke
|
||||
7. Kurz-Fazit
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundidee: Was ist ein JWT?
|
||||
|
||||
**Definition (vereinfacht):**
|
||||
|
||||
Ein **JWT (JSON Web Token)** ist ein **kompakter, URL-tauglicher, digital signierter Datenblock**, der Informationen über einen Benutzer oder eine Aktion enthält (z.B. „Benutzer X ist eingeloggt und hat Rolle admin“).
|
||||
Diese Informationen sind im JSON-Format strukturiert und werden mit einem kryptografischen Verfahren signiert (und optional verschlüsselt).
|
||||
|
||||
Damit kann ein Server (oder mehrere Server) **prüfen**, ob das Token:
|
||||
- von einem vertrauenswürdigen Aussteller stammt,
|
||||
- nicht manipuliert wurde,
|
||||
- noch gültig ist (nicht abgelaufen).
|
||||
|
||||
Man kann sich einen JWT grob als „fälschungssichereren Ausweis“ für Benutzer (oder Clients) vorstellen, der vom Server ausgestellt wird und den der Client bei weiteren Anfragen vorzeigt.
|
||||
|
||||
---
|
||||
|
||||
## 2. Aufbau eines JWT
|
||||
|
||||
Ein JWT besteht aus **drei Teilen**, jeweils Base64URL-kodiert, durch Punkte getrennt:
|
||||
|
||||
```text
|
||||
HEADER.PAYLOAD.SIGNATURE
|
||||
```
|
||||
|
||||
Ein Beispiel (gekürzt, sieht in der Praxis ähnlich aus):
|
||||
|
||||
```text
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
||||
.
|
||||
eyJzdWIiOiIxMjM0NTYiLCJlbWFpbCI6Im1heC5tdXN0ZXJAbWFpbC5jb20iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MzAxNjkzNDN9
|
||||
.
|
||||
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
||||
```
|
||||
|
||||
### 2.1 Header
|
||||
|
||||
Der Header beschreibt u.a.:
|
||||
- welches Signaturverfahren genutzt wird,
|
||||
- dass es sich um ein JWT handelt.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```json
|
||||
{
|
||||
"alg": "HS256", // Algorithmus: HMAC-SHA256
|
||||
"typ": "JWT"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Payload (Claims)
|
||||
|
||||
Die Payload enthält die eigentlichen Informationen („Claims“).
|
||||
Beispiele:
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "123456", // subject: Benutzer-ID
|
||||
"email": "max.mustermann@example.com",
|
||||
"role": "admin",
|
||||
"iat": 1730169334, // issued at (Unix-Timestamp)
|
||||
"exp": 1730172934, // expiry: Ablaufzeitpunkt
|
||||
"iss": "https://auth.meine-app.de", // issuer
|
||||
"aud": "https://api.meine-app.de" // audience
|
||||
}
|
||||
```
|
||||
|
||||
Wichtige Standard-Claims (nicht verpflichtend, aber üblich):
|
||||
- `iss` – Issuer (Wer hat das Token ausgestellt?)
|
||||
- `sub` – Subject (Auf wen bezieht sich das Token? z.B. Benutzer-ID)
|
||||
- `aud` – Audience (Für welche Anwendung/Services ist das Token bestimmt?)
|
||||
- `exp` – Expiration Time (Wann läuft das Token ab?)
|
||||
- `iat` – Issued At (Wann wurde es ausgestellt?)
|
||||
- `nbf` – Not Before (Ab wann ist es gültig?)
|
||||
|
||||
Dazu kommen **anwendungsspezifische Claims**, z.B.:
|
||||
- `email`, `username`
|
||||
- `roles`: `["admin", "user"]`
|
||||
- `scope`: `"read:orders write:orders"`
|
||||
|
||||
### 2.3 Signature
|
||||
|
||||
Die Signatur wird aus dem Header + Payload + einem Geheimnis (oder Schlüssel) gebildet.
|
||||
Formel grob:
|
||||
|
||||
```text
|
||||
signature = Sign( base64Url(header) + "." + base64Url(payload), secret_or_private_key )
|
||||
```
|
||||
|
||||
Beim Prüfen:
|
||||
- rekonstruiert der Server diese Signatur aus Header+Payload,
|
||||
- vergleicht sie mit der Signatur im Token,
|
||||
- wenn sie übereinstimmen → Token wurde nicht manipuliert.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 JWT vs klassische Session-Cookies
|
||||
|
||||
**Klassische Session:**
|
||||
- Nach Login erzeugt der Server eine **Session-ID** und speichert alle Infos (Benutzer, Rollen usw.) **im Server-Speicher** (in Memory, Redis, DB).
|
||||
- Die Session-ID wird im Browser z.B. als Cookie gespeichert.
|
||||
- Bei jeder Anfrage sendet der Browser die Session-ID; Server schaut im eigenen Speicher nach, welche Daten dazu gehören.
|
||||
|
||||
**JWT:**
|
||||
- Nach Login erzeugt der Server ein **Token**, das die Infos (Benutzer, Rollen, Ablaufzeit) **direkt im Token** enthält.
|
||||
- Das Token wird typischerweise im Browser (Cookie oder Speicher der App) gehalten.
|
||||
- Bei jeder Anfrage schickt der Client das Token mit (z.B. via HTTP-Header `Authorization: Bearer <token>`).
|
||||
- Der Server liest und prüft das Token, braucht aber keinen Session-Speicher.
|
||||
|
||||
Kurz:
|
||||
|
||||
- Session: **zustandsbehaftet (stateful)** auf Server-Seite.
|
||||
- JWT: **zustandslos (stateless)** auf Server-Seite.
|
||||
|
||||
### 3.2 JWT vs OAuth2 / OpenID Connect
|
||||
|
||||
- **OAuth2** ist ein **Protokoll für Autorisierung** (wer darf was). Es definiert Flows, Rollen (Client, Resource Owner, Authorization Server, Resource Server) etc.
|
||||
- **OpenID Connect (OIDC)** baut auf OAuth2 auf und regelt **Authentifizierung** (wer bist du) inklusive ID-Token.
|
||||
|
||||
JWT ist hier eher ein **Datenformat**:
|
||||
- In OAuth2/OIDC werden oft **Access Tokens** und **ID Tokens** als JWT umgesetzt.
|
||||
- Aber OAuth2/OIDC kann theoretisch auch andere Tokenformate verwenden.
|
||||
|
||||
**Merksatz:**
|
||||
OAuth2 / OIDC = das Protokoll;
|
||||
JWT = ein mögliches Format für die Tokens.
|
||||
|
||||
### 3.3 JWT vs JWS / JWE
|
||||
|
||||
- **JWS (JSON Web Signature)**: Standard für signierte JSON-Daten.
|
||||
- **JWE (JSON Web Encryption)**: Standard für verschlüsselte JSON-Daten.
|
||||
|
||||
Ein **typisches JWT ist ein JWS**: signiert, aber nicht verschlüsselt.
|
||||
Es gibt aber auch **JWTs als JWE**: zusätzlich verschlüsselt (seltener in der Praxis).
|
||||
|
||||
Wichtig:
|
||||
**Signiert heißt: Integrität und Echtheit** (nicht manipuliert, vom richtigen Aussteller),
|
||||
**nicht automatisch: Geheimhaltung**.
|
||||
Payload ist bei normalen JWTs **lesbar**, auch wenn sie signiert sind.
|
||||
|
||||
### 3.4 JWT vs API-Schlüssel (API Key)
|
||||
|
||||
- Ein **API-Key** ist meist ein zufälliger String, der einen Client identifiziert.
|
||||
Logik: „Wer den Key kennt, darf die API nutzen.“
|
||||
- Ein **JWT** enthält strukturierte Informationen (Claims) und ist signiert, so dass man viele Details im Token selbst hat (Benutzer, Rollen, Ablauf).
|
||||
|
||||
JWT ist in der Regel **ausdrucksstärker** und besser integrierbar in komplexe Auth-Zusammenhänge; ein API-Key ist eher „simple Zugriffskarte“.
|
||||
|
||||
### 3.5 JWT vs SAML
|
||||
|
||||
- **SAML** ist ein älterer Standard für Single Sign-On (SSO), benutzt XML statt JSON.
|
||||
- JWT (bzw. OIDC mit JWT) ist moderner, leichtergewichtig, JSON-basiert.
|
||||
|
||||
Viele moderne Anwendungen bevorzugen OIDC + JWT statt SAML, vor allem im Web-/API-Bereich.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme werden mit JWT gelöst?
|
||||
|
||||
### 4.1 Skalierbare Authentifizierung in verteilten Systemen
|
||||
|
||||
Problem:
|
||||
- Klassische Sessions erfordern zentralen Server-Speicher (Session Store).
|
||||
- In modernen Microservice-Architekturen und skalierenden Web-Apps (viele Instanzen) ist das unpraktisch.
|
||||
|
||||
JWT-Lösung:
|
||||
- **Zustandslose Tokens**: Jeder Service kann das Token **selbst verifizieren**, ohne zentralen Session Store.
|
||||
- Mehrere Instanzen, mehrere Services, sogar andere Systeme (Partner-APIs) können mit dem gleichen Token arbeiten, solange sie den Signaturschlüssel kennen (oder das zugehörige Public Key).
|
||||
|
||||
### 4.2 Einfache Weitergabe von Benutzerinformationen
|
||||
|
||||
Problem:
|
||||
- Mehrere Services müssen wissen, wer der Benutzer ist und welche Rechte er hat.
|
||||
- Man möchte nicht jedes Mal eine zusätzliche Datenbankabfrage machen.
|
||||
|
||||
JWT-Lösung:
|
||||
- Claims im Token: `sub`, `email`, `roles`, `scope`, …
|
||||
- Jeder Service liest diese Claims aus dem Token und kann basierend darauf entscheiden.
|
||||
|
||||
### 4.3 Mobile / Single-Page-Applications (SPAs)
|
||||
|
||||
Problem:
|
||||
- SPAs (z.B. React, Angular, Vue) und Mobile-Apps sprechen meist direkt mit APIs.
|
||||
- Klassische serverseitige Sessions sind dafür unhandlich, weil es keinen klassischen Browser-Request/Response-Cookie-Workflow gibt oder dieser komplexer ist.
|
||||
|
||||
JWT-Lösung:
|
||||
- Client erhält nach Login ein **Access Token (JWT)**.
|
||||
- Bei jedem API-Request wird das JWT im `Authorization`-Header mitgeschickt.
|
||||
- APIs können unabhängig vom Frontend arbeiten (auch mehrere Frontends nutzen die gleiche API).
|
||||
|
||||
---
|
||||
|
||||
## 5. Praxisnahe Anwendungsbeispiele
|
||||
|
||||
### 5.1 Web-App / API-Login mit JWT
|
||||
|
||||
Ablauf:
|
||||
|
||||
1. Benutzer gibt E-Mail + Passwort ein und klickt auf „Login“.
|
||||
2. Frontend schickt diese Daten an `/auth/login` (Backend).
|
||||
3. Backend prüft:
|
||||
- Benutzer existiert?,
|
||||
- Passwort korrekt?
|
||||
4. Wenn ok:
|
||||
- Backend erstellt ein JWT:
|
||||
- `sub = Benutzer-ID`
|
||||
- `email = Benutzer-Email`
|
||||
- `role = "user"`
|
||||
- `exp = in 15 Minuten`
|
||||
- signiert das Token mit einem geheimen Schlüssel (z.B. HS256).
|
||||
5. Backend schickt das Token an das Frontend zurück.
|
||||
6. Frontend speichert das Token:
|
||||
- z.B. in einem **httpOnly Cookie** (empfohlen),
|
||||
- oder in einem sicheren Storage (abhängig vom Setup).
|
||||
7. Bei jeder API-Anfrage:
|
||||
- schickt das Frontend das Token mit, z.B.:
|
||||
|
||||
```http
|
||||
GET /api/orders
|
||||
Authorization: Bearer <JWT_HIER>
|
||||
```
|
||||
|
||||
8. Die API:
|
||||
- prüft die Signatur,
|
||||
- prüft `exp` (nicht abgelaufen?),
|
||||
- liest `sub`, `role`, `scope` aus,
|
||||
- entscheidet, ob Zugriff erlaubt ist.
|
||||
|
||||
### 5.2 Microservices hinter einem API Gateway
|
||||
|
||||
Stell dir eine Plattform mit mehreren Microservices vor:
|
||||
|
||||
- `User-Service`
|
||||
- `Order-Service`
|
||||
- `Billing-Service`
|
||||
|
||||
Es gibt einen **Auth-Service**, der JWTs ausstellt.
|
||||
Alle Services kennen den Public Key (bei asymmetrischer Signatur) oder den Secret Key (bei symmetrischer Signatur).
|
||||
|
||||
Ablauf:
|
||||
|
||||
- Benutzer loggt sich über Auth-Service ein → bekommt JWT.
|
||||
- Frontend ruft `Order-Service` auf mit `Authorization: Bearer <JWT>`.
|
||||
- `Order-Service` prüft JWT:
|
||||
- gültig?
|
||||
- `scope` beinhaltet `read:orders`?
|
||||
- Wenn ja → Bestellung anzeigen.
|
||||
|
||||
Vorteil:
|
||||
Jeder Service muss keinen eigenen Session-Store haben, sondern nur das JWT validieren.
|
||||
|
||||
### 5.3 E-Mail-Bestätigung / Passwort-Reset
|
||||
|
||||
JWTs eignen sich auch für **zeitlich begrenzte Links**:
|
||||
|
||||
Beispiel: Passwort-Reset:
|
||||
|
||||
1. Benutzer klickt „Passwort vergessen“.
|
||||
2. Backend erzeugt ein JWT mit Claims:
|
||||
- `sub = Benutzer-ID`
|
||||
- `exp = in 30 Minuten`
|
||||
- `purpose = "password_reset"`
|
||||
3. Backend verschickt E-Mail mit Link:
|
||||
|
||||
```text
|
||||
https://meine-app.de/reset-password?token=<JWT_HIER>
|
||||
```
|
||||
|
||||
4. Benutzer klickt den Link.
|
||||
5. Frontend schickt den Token an das Backend, z.B. POST `/auth/reset-password`.
|
||||
6. Backend prüft:
|
||||
- Signatur,
|
||||
- `exp` (noch gültig?),
|
||||
- `purpose == "password_reset"`.
|
||||
7. Wenn alles passt → Passwort darf geändert werden.
|
||||
|
||||
---
|
||||
|
||||
## 6. Herausforderungen und Fallstricke
|
||||
|
||||
JWTs lösen einige Probleme, bringen aber eigene Herausforderungen mit sich.
|
||||
|
||||
### 6.1 Sicherheit bei Signatur und Schlüssel
|
||||
|
||||
Wichtige Punkte:
|
||||
|
||||
- **Algorithmuswahl:**
|
||||
Typisch:
|
||||
- Symmetrisch: `HS256` (ein gemeinsames Secret für Signatur & Prüfung)
|
||||
- Asymmetrisch: `RS256` (privater Schlüssel signiert, öffentlicher Schlüssel prüft)
|
||||
- **Keys sicher verwalten:**
|
||||
- nicht im Code hardcoden,
|
||||
- in sicheren Secret-Stores halten,
|
||||
- Schlüssel regelmäßig rotieren.
|
||||
|
||||
Gefährliche Anti-Pattern:
|
||||
- `alg: none` akzeptieren (nie tun),
|
||||
- Server so konfigurieren, dass er das `alg` aus dem Token blind vertraut (statt fest vorzugeben, was erlaubt ist).
|
||||
|
||||
### 6.2 Token-Ablauf und „Logout“
|
||||
|
||||
JWT ist stateless – das macht „Logout“ tricky:
|
||||
|
||||
- Klassische Session:
|
||||
- Session im Server-Speicher löschen → Benutzer abgemeldet.
|
||||
- JWT:
|
||||
- Token ist selbstständig gültig, bis `exp` erreicht ist.
|
||||
- Serverseitig gibt es **keine zentrale Liste** aller aktiven JWTs.
|
||||
|
||||
Mögliche Lösungen:
|
||||
- Tokens **kurzlebig** machen, z.B. 5–15 Minuten.
|
||||
- Für längere Sessions:
|
||||
- **Access Token** kurzlebig,
|
||||
- **Refresh Token** längerlebig und serverseitig verwaltbar (z.B. in DB),
|
||||
- bei Logout → Refresh Token invalidieren, Access Token läuft automatisch bald ab.
|
||||
- Optional: Blacklists/Blocklists oder Token-Revocation-Mechanismen.
|
||||
|
||||
### 6.3 Wo speichert man JWT im Browser?
|
||||
|
||||
Varianten:
|
||||
- **httpOnly Cookie**:
|
||||
- Vorteil: nicht via JavaScript auslesbar → Schutz gegen XSS.
|
||||
- Nachteil: CSRF muss beachtet werden (CSRF-Protection implementieren).
|
||||
- **localStorage / sessionStorage**:
|
||||
- Vorteil: einfache Handhabung im JS-Code.
|
||||
- Nachteil: bei XSS kann ein Angreifer das Token auslesen → sehr riskant.
|
||||
|
||||
In der Praxis:
|
||||
- Oft Kombination aus httpOnly Cookies + CSRF-Schutz,
|
||||
- oder Speziallösungen mit sog. „Double Submit Cookies“, „SameSite“-Cookies etc.
|
||||
|
||||
### 6.4 Token sind nicht geheim (wenn nur signiert)
|
||||
|
||||
Wichtiger Punkt:
|
||||
- Ein **normaler signierter JWT ist nicht verschlüsselt**.
|
||||
- Jeder, der das Token sieht, kann die Payload (Claims) lesen, da sie Base64URL-kodiert ist (was keine Verschlüsselung ist).
|
||||
|
||||
Daraus folgt:
|
||||
- Keine hochsensiblen Daten in die Payload packen (z.B. Passwörter, Zahlungsdaten, interne Geheimnisse).
|
||||
- JWT nur über **HTTPS** übertragen, damit niemand auf dem Weg „mithören“ kann.
|
||||
- Wenn wirklich Geheimhaltung nötig ist → **JWE (verschlüsseltes JWT)** oder andere Verschlüsselungsmechanismen.
|
||||
|
||||
### 6.5 Token-Größe
|
||||
|
||||
Wenn man sehr viele Claims ins Token packt:
|
||||
- wird es groß,
|
||||
- verursacht mehr Traffic,
|
||||
- passt evtl. schlechter in Cookies oder Header.
|
||||
|
||||
Praktisch:
|
||||
Nur wirklich relevante Informationen ins JWT aufnehmen:
|
||||
- Benutzer-ID
|
||||
- Rollen, Rechte oder Scope
|
||||
- wenige zusätzliche Angaben
|
||||
|
||||
Den Rest kann man bei Bedarf aus der Datenbank abfragen.
|
||||
|
||||
### 6.6 Fehler bei der Prüfung des Tokens
|
||||
|
||||
Beim Validieren darf man nicht nur die Signatur prüfen, sondern auch:
|
||||
|
||||
- Ist das Token abgelaufen? (`exp`)
|
||||
- Ist das Token noch nicht gültig? (`nbf`, `iat`)
|
||||
- Stimmt `iss` (Issuer) mit dem erwarteten Aussteller überein?
|
||||
- Stimmt `aud` (Audience) mit der eigenen Anwendung/API überein?
|
||||
|
||||
Beispiel (Node.js mit `jsonwebtoken`):
|
||||
|
||||
```js
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const token = '...'; // vom Client erhalten
|
||||
const publicKey = '...'; // Public Key (bei RS256) oder Secret (bei HS256)
|
||||
|
||||
try {
|
||||
const payload = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
issuer: 'https://auth.meine-app.de',
|
||||
audience: 'https://api.meine-app.de',
|
||||
});
|
||||
|
||||
console.log('Token gültig, Benutzer-ID:', payload.sub);
|
||||
} catch (err) {
|
||||
console.error('Ungültiges Token:', err.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Kurz-Fazit
|
||||
|
||||
- Ein **JWT** ist ein **signierter, kompakter Datenträger im JSON-Format**, der Claims über Benutzer/Clients enthält.
|
||||
- Er wird verwendet für:
|
||||
- Authentifizierung (z.B. „Benutzer X ist eingeloggt“),
|
||||
- Autorisierung (z.B. „Benutzer X hat die Rolle admin“),
|
||||
- zeitlich begrenzte Aktionen (Passwort-Reset, E-Mail-Bestätigung),
|
||||
- verteilte Systeme (Microservices), die ohne zentralen Session-Speicher auskommen sollen.
|
||||
- JWTs sind besonders sinnvoll in:
|
||||
- Web-APIs,
|
||||
- SPAs,
|
||||
- mobilen Apps,
|
||||
- Microservice- und Cloud-Architekturen.
|
||||
- Abgrenzung:
|
||||
- JWT ≠ OAuth2/OIDC (Protokoll vs. Tokenformat),
|
||||
- JWT ≠ Session-Cookie (zustandslos vs. zustandsbehaftet),
|
||||
- JWTs sind meist JWS (signiert, aber nicht verschlüsselt).
|
||||
- Herausforderungen:
|
||||
- Sichere Schlüsselverwaltung, korrekte Token-Prüfung,
|
||||
- Umgang mit Logout/Token-Invalidierung,
|
||||
- sicherer Speicherort im Client (XSS/CSRF),
|
||||
- kein Hineinpacken hochsensibler Daten.
|
||||
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
## Inhaltsverzeichnis
|
||||
- [Grundkonzepte](#grundkonzepte)
|
||||
- [Schritt 1: Welche Endpoints gibt es?](#schritt-1-welche-endpoints-gibt-es)
|
||||
- [Schritt 2: Endpoint-Konfiguration prüfen](#schritt-2-endpoint-konfiguration-prüfen)
|
||||
- [Schritt 3: Endpoint erreichbar machen](#schritt-3-endpoint-erreichbar-machen)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Grundkonzepte
|
||||
|
||||
### Was ist ein Endpoint?
|
||||
Ein **Endpoint** ist eine URL, über die Sie auf eine Anwendung in Kubernetes zugreifen können (z.B. `https://jupyterhub.example.com` oder `http://localhost:8080`).
|
||||
|
||||
### Wichtige Kubernetes-Komponenten
|
||||
|
||||
#### 1. Pod
|
||||
- Ein **Pod** ist die kleinste Einheit in Kubernetes
|
||||
- Enthält einen oder mehrere Container (z.B. Ihre Anwendung)
|
||||
- Hat eine interne IP-Adresse im Cluster
|
||||
- Befehl: `kubectl get pods -n <namespace>`
|
||||
|
||||
#### 2. Service
|
||||
- Ein **Service** ist eine stabile Zugangsadresse zu einem oder mehreren Pods
|
||||
- Macht Pods innerhalb des Clusters erreichbar
|
||||
- Typen:
|
||||
- `ClusterIP`: Nur innerhalb des Clusters erreichbar
|
||||
- `NodePort`: Von außen über Port am Node erreichbar
|
||||
- `LoadBalancer`: Erstellt einen externen Load Balancer (z.B. bei AWS)
|
||||
- Befehl: `kubectl get svc -n <namespace>`
|
||||
|
||||
#### 3. Ingress
|
||||
- Ein **Ingress** ist die "Haustür" zu Ihren Services von außerhalb des Clusters
|
||||
- Ermöglicht HTTP/HTTPS-Zugriff über Domainnamen (z.B. `app.example.com`)
|
||||
- Benötigt einen Ingress Controller (z.B. [[nginx]])
|
||||
- Befehl: `kubectl get ingress -n <namespace>`
|
||||
|
||||
#### 4. Namespace
|
||||
- Ein **Namespace** ist wie ein Ordner in Kubernetes
|
||||
- Trennt verschiedene Anwendungen oder Teams
|
||||
- Beispiele: `openmetadata`, `jupyterhub`, `default`
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: Welche Endpoints gibt es?
|
||||
|
||||
### 1.1 Alle Namespaces auflisten
|
||||
|
||||
```bash
|
||||
kubectl get namespaces
|
||||
```
|
||||
|
||||
**Erklärung:** Zeigt alle Namespaces in Ihrem Cluster. Hier finden Sie, wo Ihre Anwendungen laufen.
|
||||
|
||||
**Beispiel-Output:**
|
||||
```
|
||||
NAME STATUS AGE
|
||||
default Active 30d
|
||||
jupyterhub Active 20d
|
||||
openmetadata Active 5d
|
||||
argocd Active 25d
|
||||
```
|
||||
|
||||
### 1.2 Anwendungen in einem Namespace finden
|
||||
|
||||
```bash
|
||||
# Pods (laufende Anwendungen)
|
||||
kubectl get pods -n openmetadata
|
||||
|
||||
# Services (Zugriffspunkte)
|
||||
kubectl get svc -n openmetadata
|
||||
|
||||
# Ingress (externe Zugänge)
|
||||
kubectl get ingress -n openmetadata
|
||||
```
|
||||
|
||||
**Wichtig:** Ersetzen Sie `openmetadata` mit Ihrem Namespace!
|
||||
|
||||
**Beispiel-Output:**
|
||||
```bash
|
||||
# Pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
openmetadata-559bf987f6-gxkdk 1/1 Running 0 4d20h
|
||||
|
||||
# Services
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
openmetadata ClusterIP 172.20.25.201 <none> 8585/TCP,8586/TCP 4d20h
|
||||
|
||||
# Ingress
|
||||
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||||
openmetadata nginx openmetadata.dma.aai-dfine.de xxx.elb.amazonaws.com 80, 443 4d20h
|
||||
```
|
||||
|
||||
### 1.3 Alle Ingress-Endpoints im Cluster
|
||||
|
||||
```bash
|
||||
kubectl get ingress --all-namespaces
|
||||
```
|
||||
|
||||
**Erklärung:** Zeigt ALLE konfigurierten externen Zugänge über alle Namespaces hinweg.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Endpoint-Konfiguration prüfen
|
||||
|
||||
### 2.1 Detaillierte Ingress-Informationen
|
||||
|
||||
```bash
|
||||
kubectl describe ingress <ingress-name> -n <namespace>
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
kubectl describe ingress openmetadata -n openmetadata
|
||||
```
|
||||
|
||||
**Was Sie hier sehen:**
|
||||
- **Hosts:** Der Domainname (z.B. `openmetadata.dma.aai-dfine.de`)
|
||||
- **TLS:** Ob HTTPS konfiguriert ist und welches Zertifikat verwendet wird
|
||||
- **Backends:** Zu welchem Service und Port der Ingress weiterleitet
|
||||
- **Address:** Der tatsächliche Load Balancer
|
||||
|
||||
**Beispiel-Output:**
|
||||
```
|
||||
Name: openmetadata
|
||||
Namespace: openmetadata
|
||||
Address: aa83990c7ad3548c4b0e28680b0952ba-1924426268.eu-central-1.elb.amazonaws.com
|
||||
Ingress Class: nginx
|
||||
TLS:
|
||||
aai-dfine-certificate-tls terminates openmetadata.dma.aai-dfine.de
|
||||
Rules:
|
||||
Host Path Backends
|
||||
---- ---- --------
|
||||
openmetadata.dma.aai-dfine.de / openmetadata:8585 (10.10.1.86:8585)
|
||||
```
|
||||
|
||||
### 2.2 Pod-Status überprüfen
|
||||
|
||||
```bash
|
||||
kubectl get pods -n <namespace>
|
||||
```
|
||||
|
||||
**Status-Bedeutungen:**
|
||||
- `Running`: ✅ Pod läuft normal
|
||||
- `Pending`: ⏳ Pod startet gerade
|
||||
- `CrashLoopBackOff`: ❌ Pod startet immer wieder neu (Fehler!)
|
||||
- `Error`: ❌ Pod hat einen Fehler
|
||||
|
||||
### 2.3 Service-Details
|
||||
|
||||
```bash
|
||||
kubectl get svc <service-name> -n <namespace> -o wide
|
||||
```
|
||||
|
||||
**Zeigt:** ClusterIP, Ports, Selectors (welche Pods werden angesprochen)
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: Endpoint erreichbar machen
|
||||
|
||||
Es gibt **drei Methoden**, um auf einen Kubernetes-Endpoint zuzugreifen:
|
||||
|
||||
### Methode 1: Port-Forwarding (Empfohlen für Tests) ⭐
|
||||
|
||||
**Wann nutzen?**
|
||||
- Schneller Zugriff für Tests
|
||||
- DNS ist nicht konfiguriert
|
||||
- Lokale Entwicklung
|
||||
|
||||
**Vorteile:**
|
||||
- ✅ Keine Cluster-Änderungen
|
||||
- ✅ Sicher (nur für Sie erreichbar)
|
||||
- ✅ Sofort verfügbar
|
||||
- ✅ Keine DNS-Konfiguration nötig
|
||||
|
||||
**Nachteile:**
|
||||
- ❌ Terminal muss offen bleiben
|
||||
- ❌ Nur von Ihrem PC erreichbar
|
||||
|
||||
**Befehl:**
|
||||
```bash
|
||||
kubectl port-forward -n <namespace> svc/<service-name> <lokaler-port>:<service-port>
|
||||
```
|
||||
|
||||
**Beispiel für OpenMetadata:**
|
||||
```bash
|
||||
kubectl port-forward -n openmetadata svc/openmetadata 8585:8585
|
||||
```
|
||||
|
||||
**Was passiert:**
|
||||
1. Kubernetes erstellt einen Tunnel von Ihrem PC zum Service
|
||||
2. Sie können nun auf `http://localhost:8585` zugreifen
|
||||
3. Der Tunnel leitet alle Anfragen an den Service im Cluster weiter
|
||||
|
||||
**Zugriff:**
|
||||
```
|
||||
http://localhost:8585
|
||||
```
|
||||
|
||||
**Beenden:** `Ctrl + C` im Terminal
|
||||
|
||||
**Tipp:** Im Hintergrund laufen lassen:
|
||||
```bash
|
||||
# Windows PowerShell
|
||||
Start-Process kubectl -ArgumentList "port-forward -n openmetadata svc/openmetadata 8585:8585" -WindowStyle Hidden
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Methode 2: Über Ingress mit DNS (Produktiv-Umgebung)
|
||||
|
||||
**Wann nutzen?**
|
||||
- Produktiv-Zugriff
|
||||
- Mehrere Nutzer
|
||||
- HTTPS/TLS erforderlich
|
||||
|
||||
**Voraussetzungen:**
|
||||
1. ✅ Ingress ist konfiguriert (siehe Schritt 2.1)
|
||||
2. ✅ DNS-Eintrag existiert
|
||||
3. ✅ TLS-Zertifikat ist vorhanden
|
||||
|
||||
#### 2.1 DNS-Auflösung testen
|
||||
|
||||
```bash
|
||||
# Windows PowerShell
|
||||
nslookup openmetadata.dma.aai-dfine.de
|
||||
|
||||
# Alternative
|
||||
Resolve-DnsName openmetadata.dma.aai-dfine.de
|
||||
```
|
||||
|
||||
**Wenn DNS funktioniert:**
|
||||
```
|
||||
Server: dns.google
|
||||
Address: 8.8.8.8
|
||||
|
||||
Name: openmetadata.dma.aai-dfine.de
|
||||
Address: 54.xxx.xxx.xxx
|
||||
```
|
||||
|
||||
**Wenn DNS NICHT funktioniert:**
|
||||
```
|
||||
*** dns.google kann openmetadata.dma.aai-dfine.de nicht finden: Non-existent domain
|
||||
```
|
||||
|
||||
#### 2.2 Problem: DNS funktioniert nicht
|
||||
|
||||
**Lösung A: Lokale Hosts-Datei (temporär für Tests)**
|
||||
|
||||
1. **Load Balancer IP/Hostname finden:**
|
||||
```bash
|
||||
kubectl get ingress <ingress-name> -n <namespace>
|
||||
```
|
||||
|
||||
Notieren Sie die `ADDRESS` (z.B. `xxx.elb.amazonaws.com`)
|
||||
|
||||
2. **Load Balancer IP auflösen:**
|
||||
```bash
|
||||
nslookup xxx.elb.amazonaws.com
|
||||
```
|
||||
|
||||
3. **Windows Hosts-Datei bearbeiten:**
|
||||
- Datei öffnen als Administrator: `C:\Windows\System32\drivers\etc\hosts`
|
||||
- Zeile hinzufügen:
|
||||
```
|
||||
<IP-Adresse> openmetadata.dma.aai-dfine.de
|
||||
```
|
||||
- Speichern
|
||||
|
||||
4. **Im Browser öffnen:**
|
||||
```
|
||||
https://openmetadata.dma.aai-dfine.de
|
||||
```
|
||||
|
||||
**Lösung B: DNS-Eintrag erstellen lassen (dauerhaft)**
|
||||
|
||||
Jemand mit AWS Route53-Zugriff muss einen DNS-Eintrag erstellen:
|
||||
- **Typ:** CNAME oder A-Record
|
||||
- **Name:** `openmetadata.dma.aai-dfine.de`
|
||||
- **Ziel:** Load Balancer Hostname/IP
|
||||
|
||||
---
|
||||
|
||||
### Methode 3: Direkt zum Load Balancer (ohne DNS)
|
||||
|
||||
**Wenn vorhanden:**
|
||||
|
||||
1. **Load Balancer URL finden:**
|
||||
```bash
|
||||
kubectl get ingress <ingress-name> -n <namespace>
|
||||
```
|
||||
|
||||
2. **Direkt mit curl testen:**
|
||||
```bash
|
||||
curl -H "Host: openmetadata.dma.aai-dfine.de" http://<load-balancer-url>
|
||||
```
|
||||
|
||||
**Hinweis:** Funktioniert nicht immer im Browser (Host-Header erforderlich)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: "Hmmm... can't reach this page" / ERR_NAME_NOT_RESOLVED
|
||||
|
||||
**Ursache:** DNS-Name kann nicht aufgelöst werden
|
||||
|
||||
**Lösung:**
|
||||
1. DNS testen: `nslookup <hostname>`
|
||||
2. Falls DNS fehlt → **Port-Forwarding** verwenden (Methode 1)
|
||||
3. Oder: Hosts-Datei editieren (Methode 2, Lösung A)
|
||||
|
||||
---
|
||||
|
||||
### Problem: Port-Forwarding schlägt fehl
|
||||
|
||||
**Fehlermeldung:** `Unable to listen on port 8585`
|
||||
|
||||
**Ursache:** Port ist bereits belegt
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Anderen lokalen Port verwenden
|
||||
kubectl port-forward -n openmetadata svc/openmetadata 9999:8585
|
||||
|
||||
# Dann öffnen: http://localhost:9999
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Pod läuft nicht (Status: CrashLoopBackOff)
|
||||
|
||||
**Logs anschauen:**
|
||||
```bash
|
||||
kubectl logs <pod-name> -n <namespace>
|
||||
|
||||
# Letzte 100 Zeilen
|
||||
kubectl logs <pod-name> -n <namespace> --tail=100
|
||||
|
||||
# Vorheriger Container (wenn Pod neu gestartet wurde)
|
||||
kubectl logs <pod-name> -n <namespace> --previous
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: 502 Bad Gateway / 503 Service Unavailable
|
||||
|
||||
**Ursache:** Service erreicht den Pod nicht
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# 1. Läuft der Pod?
|
||||
kubectl get pods -n <namespace>
|
||||
|
||||
# 2. Sind Endpoints vorhanden?
|
||||
kubectl get endpoints <service-name> -n <namespace>
|
||||
|
||||
# 3. Service-Details
|
||||
kubectl describe svc <service-name> -n <namespace>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Authentifizierung erforderlich
|
||||
|
||||
**Wenn Login-Seite erscheint:**
|
||||
|
||||
**Standard-Credentials suchen:**
|
||||
```bash
|
||||
# Secrets auflisten
|
||||
kubectl get secrets -n <namespace>
|
||||
|
||||
# Secret anschauen (Werte sind base64-kodiert)
|
||||
kubectl get secret <secret-name> -n <namespace> -o yaml
|
||||
|
||||
# Secret dekodieren
|
||||
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.password}' | base64 -d
|
||||
```
|
||||
|
||||
**Beispiel OpenMetadata:**
|
||||
- Standard-User: `admin`
|
||||
- Standard-Password: `admin`
|
||||
|
||||
---
|
||||
|
||||
## Nützliche Befehle - Cheat Sheet
|
||||
|
||||
### Übersicht verschaffen
|
||||
```bash
|
||||
# Alle Namespaces
|
||||
kubectl get namespaces
|
||||
|
||||
# Alles in einem Namespace
|
||||
kubectl get all -n <namespace>
|
||||
|
||||
# Alle Ingress-Endpoints
|
||||
kubectl get ingress --all-namespaces
|
||||
```
|
||||
|
||||
### Status prüfen
|
||||
```bash
|
||||
# Pods mit mehr Details
|
||||
kubectl get pods -n <namespace> -o wide
|
||||
|
||||
# Pod-Logs live verfolgen
|
||||
kubectl logs -f <pod-name> -n <namespace>
|
||||
|
||||
# In Pod einloggen (für Debugging)
|
||||
kubectl exec -it <pod-name> -n <namespace> -- /bin/bash
|
||||
```
|
||||
|
||||
### Konfiguration anschauen
|
||||
```bash
|
||||
# YAML eines Ingress
|
||||
kubectl get ingress <name> -n <namespace> -o yaml
|
||||
|
||||
# Service-Details
|
||||
kubectl describe svc <name> -n <namespace>
|
||||
|
||||
# ConfigMap auslesen
|
||||
kubectl get configmap <name> -n <namespace> -o yaml
|
||||
```
|
||||
|
||||
### Port-Forwarding
|
||||
```bash
|
||||
# Standard
|
||||
kubectl port-forward -n <namespace> svc/<service-name> <local-port>:<service-port>
|
||||
|
||||
# Zu einem Pod (falls Service nicht existiert)
|
||||
kubectl port-forward -n <namespace> <pod-name> <local-port>:<container-port>
|
||||
|
||||
# Alle Interfaces binden (auch im Netzwerk erreichbar)
|
||||
kubectl port-forward --address 0.0.0.0 -n <namespace> svc/<service> 8585:8585
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Für Tests und Entwicklung
|
||||
✅ **Port-Forwarding** verwenden
|
||||
- Schnell, sicher, keine Änderungen am Cluster
|
||||
|
||||
### Für Produktiv-Zugriff
|
||||
✅ **Ingress mit DNS** verwenden
|
||||
- Korrekte DNS-Einträge erstellen lassen
|
||||
- TLS/HTTPS konfigurieren
|
||||
- Basic Auth oder OAuth für Authentifizierung
|
||||
|
||||
### Sicherheit
|
||||
⚠️ **Nie direkt auf Pods zugreifen** in Produktion
|
||||
⚠️ **Immer über Services** routen
|
||||
⚠️ **Port-Forwarding nicht dauerhaft** laufen lassen
|
||||
|
||||
---
|
||||
|
||||
## Glossar
|
||||
|
||||
| Begriff | Erklärung |
|
||||
|---------|-----------|
|
||||
| **Cluster** | Die gesamte Kubernetes-Umgebung mit allen Maschinen |
|
||||
| **Node** | Eine einzelne Maschine (VM) im Cluster |
|
||||
| **Pod** | Kleinste Einheit, enthält Container mit Ihrer App |
|
||||
| **Service** | Stabiler Zugriffspunkt zu einem oder mehreren Pods |
|
||||
| **Ingress** | HTTP/HTTPS-Zugang von außen, wie ein Router |
|
||||
| **Namespace** | Logische Trennung/Ordner für Anwendungen |
|
||||
| **ConfigMap** | Konfigurationsdaten (nicht-sensitiv) |
|
||||
| **Secret** | Sensible Daten (Passwörter, Tokens) - base64-kodiert |
|
||||
| **Load Balancer** | Verteilt Traffic auf mehrere Pods (z.B. AWS ELB) |
|
||||
| **TLS/SSL** | Verschlüsselung für HTTPS |
|
||||
| **Port-Forward** | Tunnel von lokalem PC zu Kubernetes-Service |
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
### Quick Start: Auf Anwendung zugreifen
|
||||
|
||||
1. **Namespace finden:**
|
||||
```bash
|
||||
kubectl get namespaces
|
||||
```
|
||||
|
||||
2. **Ingress prüfen:**
|
||||
```bash
|
||||
kubectl get ingress -n <namespace>
|
||||
```
|
||||
|
||||
3. **Port-Forwarding starten:**
|
||||
```bash
|
||||
kubectl port-forward -n <namespace> svc/<service-name> 8585:8585
|
||||
```
|
||||
|
||||
4. **Im Browser öffnen:**
|
||||
```
|
||||
http://localhost:8585
|
||||
```
|
||||
|
||||
Das war's! 🎉
|
||||
|
||||
---
|
||||
|
||||
## Weitere Ressourcen
|
||||
|
||||
- [Offizielle Kubernetes Dokumentation](https://kubernetes.io/docs/)
|
||||
- [kubectl Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/)
|
||||
- [Kubernetes Port-Forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/)
|
||||
|
||||
---
|
||||
|
||||
*Erstellt am: {{ date:2026-02-10 }}*
|
||||
*Workspace: `gitops`*
|
||||
@@ -0,0 +1,299 @@
|
||||
#IT #Container #Open-Source #Cloud
|
||||
|
||||
---
|
||||
|
||||
## 1. Was ist Kubernetes – in einem Satz?
|
||||
|
||||
Kubernetes ist ein Open-Source‑System zur **Orchestrierung von Containern** (z. B. Docker), das die **Bereitstellung, Skalierung und Verwaltung** von Anwendungen automatisiert.
|
||||
|
||||
- Entwickelt von Google, heute von der CNCF (Cloud Native Computing Foundation) verwaltet
|
||||
- Läuft in der Cloud (AWS, Azure, GCP), im Rechenzentrum (On‑Prem) oder lokal (z. B. mit kind, minikube)
|
||||
|
||||
---
|
||||
|
||||
## 2. Warum braucht man Kubernetes?
|
||||
|
||||
### 2.1 Ausgangslage ohne Kubernetes
|
||||
|
||||
Bei klassischen Server- oder Container-Setups müssen viele Dinge manuell oder mit Skripten gelöst werden:
|
||||
|
||||
- Container starten/stoppen
|
||||
- Lastverteilung zwischen Instanzen
|
||||
- Neustart fehlerhafter Instanzen
|
||||
- Rollout neuer Versionen
|
||||
- Skalierung bei Lastspitzen
|
||||
- Konfigurationsmanagement und Secrets
|
||||
- Self-Healing bei Fehlern
|
||||
|
||||
### 2.2 Ziele von Kubernetes
|
||||
|
||||
Kubernetes löst diese Probleme, indem es:
|
||||
|
||||
1. **Deklaratives Modell** nutzt
|
||||
- Du beschreibst den „Soll-Zustand“ (z. B. „es sollen 3 Instanzen meiner App laufen“).
|
||||
- Kubernetes sorgt im Hintergrund dafür, dass dieser Zustand erreicht und gehalten wird.
|
||||
|
||||
2. **Automatisierung** von:
|
||||
- Rollouts und Rollbacks
|
||||
- Skalierung (horizontaler Ausbau: mehr Instanzen)
|
||||
- Self-Healing (Pods werden bei Absturz neu gestartet)
|
||||
- Service Discovery & Load Balancing
|
||||
|
||||
3. **Abstraktion von Infrastruktur**:
|
||||
- Egal ob deine Knoten VMs in der Cloud oder bare-metal-Server sind – Kubernetes abstrahiert das weg.
|
||||
|
||||
---
|
||||
|
||||
## 3. Hohe Ebene: Aufbau eines Kubernetes-Clusters
|
||||
|
||||
Ein Kubernetes-Cluster besteht grob aus:
|
||||
|
||||
- **Control Plane** (Master-Komponenten)
|
||||
- **Worker Nodes** (wo die Container wirklich laufen)
|
||||
|
||||
### 3.1 Control Plane (Steuerungsebene)
|
||||
|
||||
Wichtige Komponenten:
|
||||
|
||||
- **kube-apiserver**
|
||||
- Zentraler Einstiegspunkt
|
||||
- Alle Befehle von `kubectl` und anderen Komponenten laufen über die API ([[kubectl]])
|
||||
|
||||
- **etcd**
|
||||
- Verteilter Key-Value-Store
|
||||
- Hält den gesamten Cluster-Zustand (z. B. welche Pods, Deployments, ConfigMaps etc.)
|
||||
|
||||
- **kube-scheduler**
|
||||
- Entscheidet, auf welchem Node neue Pods laufen sollen
|
||||
- Berücksichtigt Ressourcen (CPU, RAM), Constraints, Affinities etc.
|
||||
|
||||
- **kube-controller-manager**
|
||||
- Mehrere Controller (z. B. für Deployments, Nodes, Endpoints)
|
||||
- Stellt sicher, dass der Ist-Zustand dem Soll-Zustand entspricht („Reconciliation Loop“)
|
||||
|
||||
- **cloud-controller-manager** (optional)
|
||||
- Integration mit Cloud-Anbietern (z. B. Load Balancer in AWS/Azure/GCP anlegen)
|
||||
|
||||
### 3.2 Worker Nodes (Arbeitsebene)
|
||||
|
||||
Auf jedem Worker laufen:
|
||||
|
||||
- **kubelet**
|
||||
- Agent auf dem Node
|
||||
- Kommuniziert mit der API und sorgt lokal dafür, dass die Pods wie gewünscht laufen
|
||||
|
||||
- **Container Runtime**
|
||||
- Führt Container aus: z. B. containerd, CRI‑O, Docker (früher direkt, heute über CRI)
|
||||
|
||||
- **kube-proxy**
|
||||
- Implementiert das Kubernetes-Service-Netzwerk
|
||||
- Kümmert sich um Weiterleitung von Netzwerkpaketen zu den richtigen Pods
|
||||
|
||||
---
|
||||
|
||||
## 4. Zentrale Konzepte in Kubernetes
|
||||
|
||||
### 4.1 Pod
|
||||
|
||||
- Kleinste deploybare Einheit in Kubernetes
|
||||
- Ein Pod enthält einen oder mehrere Container, die:
|
||||
- sich IP-Adresse und Ports teilen
|
||||
- sich lokale Volumes teilen
|
||||
- Typischerweise: 1 Container pro Pod (plus evtl. Sidecars)
|
||||
|
||||
Beispiel: Ein Pod mit einem [[NGINX]] Container:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
### 4.2 Deployment
|
||||
|
||||
- Deklarative Beschreibung von Pods + Skalierung
|
||||
- Sagt z. B.: „Es sollen 3 identische Replikate dieses Pods laufen“
|
||||
- Unterstützt Rollouts und Rollbacks (z. B. `kubectl rollout undo`)
|
||||
|
||||
Beispiel-Deployment:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: my-nginx-deployment
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: my-nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: my-nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.27
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
### 4.3 Service
|
||||
|
||||
- Stabile Netzwerk-Adresse (DNS-Name, virtuelle IP) für eine Gruppe von Pods
|
||||
- Entkoppelt Clients von einzelnen Pod-IP-Adressen
|
||||
- Typen:
|
||||
- **ClusterIP**: intern im Cluster
|
||||
- **NodePort**: nach außen freigegeben über einen Port jedes Nodes
|
||||
- **LoadBalancer**: Integration mit einem Cloud-Load-Balancer
|
||||
|
||||
Beispiel-Service:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: my-nginx-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: my-nginx
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
```
|
||||
|
||||
### 4.4 Namespace
|
||||
|
||||
- Logische Unterteilung innerhalb eines Clusters
|
||||
- Dient zur:
|
||||
- Trennung von Umgebungen (dev, test, prod)
|
||||
- Multi-Tenancy
|
||||
- Ressourcenkontrolle (ResourceQuotas, Limits)
|
||||
- Standard-Namespace: `default`, daneben z. B. `kube-system`, `kube-public`
|
||||
|
||||
### 4.5 ConfigMap & Secret
|
||||
|
||||
- **ConfigMap**: Konfiguration (nicht sensibel), z. B. Properties, ENV-Variablen
|
||||
- **Secret**: sensible Daten (Passwörter, Tokens, Zertifikate)
|
||||
- Werden in Pods als ENV oder als Files (Volumes) bereitgestellt
|
||||
|
||||
### 4.6 Volume & PersistentVolume
|
||||
|
||||
- **Volumes**: Daten, die nicht verschwinden, wenn ein Container neu startet
|
||||
- **PersistentVolume (PV)**: repräsentiert eine physische/Cloud-Speicher-Ressource
|
||||
- **PersistentVolumeClaim (PVC)**: „Anforderung“ eines Pods an Speicher (Größe, Klasse)
|
||||
|
||||
---
|
||||
|
||||
## 5. Wie arbeitet Kubernetes? – Das „Desired State“-Modell
|
||||
|
||||
1. Du definierst Ressourcen (YAML/JSON), z. B.:
|
||||
|
||||
- „Deployment mit 3 Replikas meiner App“
|
||||
- „Service, der auf Port 80 erreichbar ist“
|
||||
|
||||
2. Du übernimmt diese Definition per:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
```
|
||||
|
||||
3. Die API speichert diese Definition in **etcd**.
|
||||
|
||||
4. Controller und Scheduler beobachten den Zustand und führen eine **Reconciliation Loop** aus:
|
||||
|
||||
- Ist-Zustand (z. B. 2 Pods laufen) vs. Soll-Zustand (3 Pods sollen laufen)
|
||||
- Kubernetes startet einen weiteren Pod
|
||||
- Fällt ein Pod aus → wird automatisch ersetzt
|
||||
|
||||
---
|
||||
|
||||
## 6. Typischer Entwickler-Workflow mit Kubernetes
|
||||
|
||||
1. **Container-Image bauen**
|
||||
- z. B. mit Docker oder Buildpacks
|
||||
|
||||
2. **Image registrieren**
|
||||
- In einer Container Registry (Docker Hub, GitHub Container Registry, ECR, GCR…)
|
||||
|
||||
3. **Kubernetes-Manifest(e) schreiben**
|
||||
- Deployment, Service, ConfigMap, Secret, etc.
|
||||
|
||||
4. **Deployen**
|
||||
- `kubectl apply -f .`
|
||||
- Oder über CI/CD-Pipelines (GitLab CI, GitHub Actions, Argo CD, Jenkins etc.)
|
||||
|
||||
5. **Monitoring & Logging**
|
||||
- Über Tools wie Prometheus, Grafana, ELK/EFK-Stack, Loki, etc.
|
||||
|
||||
---
|
||||
|
||||
## 7. Kubernetes im Ökosystem / Erweiterungen
|
||||
|
||||
Kubernetes ist eine Basis, auf der vieles aufbaut:
|
||||
|
||||
- **Helm**
|
||||
- „Package Manager“ für Kubernetes
|
||||
- Erleichtert das Installieren komplexer Anwendungen (Charts)
|
||||
|
||||
- **Ingress & Ingress Controller**
|
||||
- HTTP/HTTPS-Routing auf Services
|
||||
- Ermöglicht virtuelle Hosts, Pfad-basiertes Routing usw.
|
||||
|
||||
- **Service Mesh (z. B. Istio, Linkerd)**
|
||||
- Feinere Kontrolle über Traffic, Observability, Security (mTLS)
|
||||
|
||||
- **Operators**
|
||||
- Kubernetes-Erweiterungen, die komplexe Anwendungen als „Custom Resources“ managen (z. B. Datenbanken)
|
||||
|
||||
- **GitOps (z. B. Argo CD, Flux)**
|
||||
- Zustände des Clusters werden aus Git-Repositories synchronisiert
|
||||
|
||||
---
|
||||
|
||||
## 8. Vorteile von Kubernetes
|
||||
|
||||
- Hohe **Portabilität** über verschiedene Infrastrukturen
|
||||
- **Automatisierte Skalierung** und Self-Healing
|
||||
- **Standardisierte Schnittstellen** (API-first)
|
||||
- Große **Community** und reiches Ökosystem
|
||||
- Gut geeignet für **Microservices** und verteile Systeme
|
||||
|
||||
---
|
||||
|
||||
## 9. Herausforderungen und Komplexität
|
||||
|
||||
- **Steile Lernkurve**: viele Konzepte (Pods, Deployments, Services, Ingress, RBAC, Storage…)
|
||||
- **Betrieb** eines eigenen Clusters ist nicht trivial
|
||||
- Viele Unternehmen nutzen Managed Services: GKE, AKS, EKS, OpenShift, Rancher etc.
|
||||
- **Observability**: Logs, Metrics, Tracing sind komplexer als bei einer einzigen VM
|
||||
- **Sicherheit** (RBAC, NetworkPolicies, Secrets-Management) erfordert Planung
|
||||
|
||||
---
|
||||
|
||||
## 10. Wie kann man praktisch starten?
|
||||
|
||||
Konkrete Schritte, um anzufangen:
|
||||
|
||||
1. **Lokale Umgebung**:
|
||||
- `kind` (Kubernetes in Docker)
|
||||
- `minikube` oder `k3d`
|
||||
|
||||
2. **Übungen**:
|
||||
- Einen einfachen Webserver als Deployment & Service bereitstellen
|
||||
- Mit `kubectl get`, `describe`, `logs` spielen
|
||||
- Scaling testen: `kubectl scale deployment my-deployment --replicas=5`
|
||||
- Eine neue Version deployen und Rollback ausprobieren
|
||||
|
||||
3. **Doku & Lernressourcen**:
|
||||
- Offizielle Doku: https://kubernetes.io/docs/
|
||||
- Interaktive Tutorials: Katacoda-ähnliche Labs, Kubernetes Bootcamps, z. B. von Cloud-Anbietern
|
||||
@@ -0,0 +1,25 @@
|
||||
„Layer‑7‑Logik“ bezieht sich auf Entscheidungen/Routing auf Basis der **Anwendungsebene** (Layer 7 im OSI-Modell).
|
||||
|
||||
Kurz zum Kontext:
|
||||
|
||||
- **Layer 3 (Netzwerk)**: IP-Adressen
|
||||
- **Layer 4 (Transport)**: TCP/UDP, Ports (z. B. Port 80, 443)
|
||||
- **Layer 7 (Anwendung)**: HTTP, HTTPS, gRPC, SMTP etc. – also „Inhalt“ der Anfrage
|
||||
|
||||
**Layer‑4‑Routing**:
|
||||
Es wird nur nach IP + Port entschieden. Beispiel:
|
||||
- Alles an `10.0.0.5:80` geht zu Service A.
|
||||
Der Proxy/Loadbalancer „sieht“ nicht, ob das `/api` oder `/shop` ist.
|
||||
|
||||
**Layer‑7‑Logik**:
|
||||
Der Proxy versteht das Protokoll (z. B. HTTP) und kann anhand von Anwendungsdaten entscheiden:
|
||||
|
||||
- Hostname: `api.example.com` → Service A, `shop.example.com` → Service B
|
||||
- Pfad: `/api` → Backend 1, `/app` → Backend 2
|
||||
- HTTP-Header: bestimmter `User-Agent`, `X-Feature-Flag` → anderes Routing
|
||||
- Cookies / Session: z. B. „User ist in A/B-Test-Gruppe → anderes Backend“
|
||||
- Authentifizierung / [[JWT token]] prüfen, bevor weitergeleitet wird
|
||||
- TLS-Termination: HTTPS entschlüsseln, weiter innen nur HTTP sprechen
|
||||
|
||||
Im Kubernetes-Ingress-Kontext heißt „Layer‑7‑Logik“ also:
|
||||
Der Ingress-Controller trifft Routing- und Sicherheitsentscheidungen **auf Basis von HTTP(S)-Details** (Host, Pfad, Header, Cookies usw.), nicht nur auf Basis von IPs und Ports.
|
||||
@@ -0,0 +1,430 @@
|
||||
## 1. Grundidee: Was ist nginx – gerade im Python‑Kontext?
|
||||
|
||||
nginx („engine x“) ist ein **Webserver** und **Reverse Proxy**, der:
|
||||
|
||||
- HTTP(S)-Anfragen aus dem Internet annimmt
|
||||
- statische Dateien (HTML, CSS, Bilder, JS) direkt ausliefert
|
||||
- Anfragen für **dynamische Inhalte** (z.B. deine Python‑App) an einen **Anwendungsserver** weiterleitet (z.B. Gunicorn, uWSGI, [[uvicorn]])
|
||||
|
||||
Im Python‑Kontext wird nginx fast immer als **„Vorderseite“ (Frontend) vor einer Python‑Webanwendung** eingesetzt.
|
||||
|
||||
Typischer Aufbau:
|
||||
|
||||
```text
|
||||
Browser des Nutzers -> nginx -> Gunicorn/uWSGI/uvicorn -> Python-App (Django, Flask, FastAPI, ...)
|
||||
(Port 80/443) (lokaler Port, z.B. 8000)
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. Einordnung in die Webwelt: Grundbegriffe
|
||||
|
||||
Damit nginx einzuordnen ist, müssen ein paar Begriffe klar sein:
|
||||
|
||||
### 2.1 Webserver
|
||||
|
||||
Ein **Webserver**:
|
||||
|
||||
- läuft auf einem Rechner (Server)
|
||||
- „lauscht“ auf Port 80 (HTTP) oder 443 (HTTPS)
|
||||
- nimmt HTTP-Anfragen entgegen und schickt HTTP-Antworten zurück
|
||||
|
||||
Beispiele:
|
||||
- **nginx**
|
||||
- **Apache HTTP Server**
|
||||
- (leichtere) Caddy, lighttpd
|
||||
|
||||
### 2.2 Application Server / WSGI-/ASGI-Server
|
||||
|
||||
Ein **Application Server** führt deine **Web-App** aus, also deinen Python-Code.
|
||||
|
||||
- Für klassische synchronen Python-Webapps: **WSGI**-Server
|
||||
- z.B. **[[Gunicorn]]**, **uWSGI**, mod_wsgi
|
||||
- Für moderne, asynchrone Apps: **ASGI**-Server
|
||||
- z.B. **[[uvicorn]]**, **[[hypercorn]]**, Daphne
|
||||
|
||||
Der App-Server:
|
||||
|
||||
- spricht mit deinem Python-Framework (Django, Flask, [[FastAPI]], etc.)
|
||||
- versteht Anfragen im WSGI/ASGI-Format
|
||||
- erzeugt dynamische Antworten (z.B. JSON, HTML aus Templates)
|
||||
|
||||
### 2.3 Python-Webframework (Django, Flask, [[FastAPI]] …)
|
||||
|
||||
Ein **Framework** ist deine Programmierbibliothek, mit der du die Web-App baust:
|
||||
|
||||
- Django: „Vollausstattung“ (ORM, Admin, Auth, etc.)
|
||||
- Flask: minimaler Kern, viel per Erweiterung
|
||||
- [[FastAPI]]: modern, async, stark bei APIs
|
||||
|
||||
**Das Framework ist nicht der Webserver.**
|
||||
Es definiert nur, wie dein Code auf Anfragen reagieren soll.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung: nginx vs. verwandte Begriffe
|
||||
|
||||
### 3.1 nginx vs. Apache
|
||||
|
||||
Beide sind **Webserver**, Unterschiede grob:
|
||||
|
||||
- **nginx**
|
||||
- schnell bei vielen gleichzeitigen Verbindungen
|
||||
- ressourcensparend (eventbasiert)
|
||||
- sehr gut als Reverse Proxy, Load Balancer, TLS-Terminator
|
||||
- **Apache**
|
||||
- älter, sehr weit verbreitet
|
||||
- modulbasiert, viel Legacy
|
||||
- kann PHP & Co. direkt ausführen (mod_php, etc.)
|
||||
|
||||
Im modernen Python-Umfeld: **nginx + Gunicorn/[[uvicorn]]** ist ein sehr gängiges Setup.
|
||||
|
||||
### 3.2 nginx vs. [[Gunicorn]]/uWSGI/[[uvicorn]]
|
||||
|
||||
- **nginx**: Webserver / Reverse Proxy
|
||||
- **[[Gunicorn]]/uWSGI/[[uvicorn]]**: Python-Application-Server
|
||||
|
||||
nginx:
|
||||
|
||||
- kümmert sich um HTTP(S), TLS, statische Dateien, Load Balancing
|
||||
- schickt Anfragen an den Application-Server weiter
|
||||
|
||||
[[Gunicorn]]/[[uvicorn]]:
|
||||
|
||||
- startet deine Python-App
|
||||
- verarbeitet die Anfragen logikseitig
|
||||
- rendert Templates, greift auf Datenbanken zu, etc.
|
||||
|
||||
### 3.3 nginx vs. Django/Flask/[[FastAPI]]
|
||||
|
||||
- nginx: Infrastruktur-Komponente (Server)
|
||||
- Django/Flask/[[FastAPI]]: Bibliotheken, mit denen du deinen Code schreibst
|
||||
|
||||
Eine typische Kombi:
|
||||
|
||||
```text
|
||||
nginx + Gunicorn + Django
|
||||
nginx + uvicorn + FastAPI
|
||||
nginx + uWSGI + Flask
|
||||
```
|
||||
|
||||
Jede Komponente hat ihren klaren Aufgabenbereich.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst nginx im Python-Setup?
|
||||
|
||||
### 4.1 Sichere und stabile Auslieferung deiner Python-Webapp
|
||||
|
||||
Problem:
|
||||
Die eingebaute Entwicklungsserver in Django/Flask sind nur für **lokale Entwicklung** gedacht:
|
||||
|
||||
- wenig performant
|
||||
- kaum Sicherheitsfeatures
|
||||
- nicht für viele gleichzeitige Nutzer ausgelegt
|
||||
|
||||
Lösung:
|
||||
nginx + App-Server (Gunicorn/uvicorn):
|
||||
|
||||
- nginx nimmt alle Anfragen entgegen
|
||||
- Python-App läuft hinter nginx auf einem internen Port
|
||||
- Anwendung ist stabiler, sicherer, skalierbarer
|
||||
|
||||
### 4.2 Auslieferung statischer Dateien
|
||||
|
||||
Problem:
|
||||
Python-Frameworks sind nicht optimiert, um Millionen von statischen Dateien schnell auszuliefern.
|
||||
|
||||
Lösung:
|
||||
|
||||
- nginx kann statische Dateien extrem effizient ausliefern
|
||||
- Python-App wird entlastet
|
||||
|
||||
**Beispiel:** Django-Setup:
|
||||
|
||||
```text
|
||||
Browser -> nginx -> (statische Dateien: /static/) direkt von nginx
|
||||
-> (dynamische URLs: /api/, /admin/) an Gunicorn/Django
|
||||
```
|
||||
|
||||
### 4.3 HTTPS / TLS-Termination
|
||||
|
||||
Problem:
|
||||
Du willst deine Seite unter **https://deinedomain.de** mit Zertifikat ausliefern.
|
||||
Python-Application-Server kümmern sich eher nicht um Zertifikate.
|
||||
|
||||
Lösung:
|
||||
|
||||
- nginx bietet HTTPS/TLS an (inkl. Let’s Encrypt-Integration)
|
||||
- entschlüsselt eingehende Anfragen
|
||||
- leitet sie dann intern als HTTP an Gunicorn/uvicorn weiter
|
||||
|
||||
So muss sich deine Python-App nicht um Zertifikate kümmern.
|
||||
|
||||
### 4.4 Load Balancing (Lastverteilung)
|
||||
|
||||
Problem:
|
||||
Eine einzelne Instanz deiner Python-App reicht nicht bei hoher Last.
|
||||
|
||||
Lösung:
|
||||
|
||||
- nginx kann Anfragen auf mehrere App-Server verteilen:
|
||||
|
||||
```text
|
||||
Browser -> nginx -> Gunicorn Instanz 1
|
||||
-> Gunicorn Instanz 2
|
||||
-> Gunicorn Instanz 3
|
||||
```
|
||||
|
||||
Damit erreichst du höhere Verfügbarkeit und Kapazität.
|
||||
|
||||
### 4.5 Reverse Proxy / Schutz der App
|
||||
|
||||
Problem:
|
||||
Du willst deine App nicht direkt ins Internet hängen (Sicherheitsrisiko, private Ports).
|
||||
|
||||
Lösung:
|
||||
|
||||
- nginx steht „davor“ und leitet nur definierte Anfragen durch
|
||||
- interne Ports (z.B. 8000) sind von außen nicht erreichbar
|
||||
- nginx kann zusätzliche Security-Header, Ratelimits, IP-Filter etc. einsetzen
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen bei der Arbeit mit nginx
|
||||
|
||||
### 5.1 Konfigurationssyntax & -struktur
|
||||
|
||||
- nginx wird über Textdateien konfiguriert (z.B. `/etc/nginx/nginx.conf` und `sites-available/...`)
|
||||
- die Syntax ist am Anfang ungewohnt:
|
||||
- `http`, `server`, `location`, `upstream`, `listen`, etc.
|
||||
- Fehler in der Konfiguration führen dazu, dass nginx nicht startet oder falsch weiterleitet
|
||||
|
||||
Praxis-Hinweise:
|
||||
|
||||
```bash
|
||||
sudo nginx -t # Konfiguration testen
|
||||
sudo systemctl reload nginx # Konfiguration neu laden
|
||||
```
|
||||
|
||||
### 5.2 Rechte & Pfade (Linux-Dateisystem)
|
||||
|
||||
Probleme:
|
||||
|
||||
- nginx läuft als bestimmter Benutzer (z.B. `www-data`)
|
||||
- Python-App läuft evtl. als anderer Benutzer (z.B. `myapp`)
|
||||
- Zugriffsrechte auf Dateien/Ordner (Logs, statische Dateien) müssen passen
|
||||
|
||||
Beispiel:
|
||||
Wenn statische Dateien unter `/var/www/myapp/static/` liegen, muss der nginx-User Lesezugriff haben.
|
||||
|
||||
### 5.3 Fehlersuche (Debugging)
|
||||
|
||||
- Fehler können an mehreren Stellen auftreten:
|
||||
- DNS / Domain falsch
|
||||
- nginx-Konfiguration fehlerhaft
|
||||
- App-Server (Gunicorn/uvicorn) läuft nicht
|
||||
- Python-App wirft Fehler
|
||||
|
||||
Praxis-Tipp:
|
||||
|
||||
- in nginx-Logs schauen:
|
||||
- `/var/log/nginx/access.log`
|
||||
- `/var/log/nginx/error.log`
|
||||
- in App-Logs schauen
|
||||
- Ports prüfen: `ss -tulpen` oder `netstat -tulpen`
|
||||
|
||||
### 5.4 Performance-Tuning
|
||||
|
||||
Fortgeschrittener, aber wichtig bei größerem Traffic:
|
||||
|
||||
- wie viele Worker-Prozesse nginx nutzt
|
||||
- Keep-Alive-Einstellungen
|
||||
- Buffergrößen
|
||||
- Cache-Konfiguration für statische Inhalte
|
||||
|
||||
Am Anfang reicht meist die Standardkonfiguration, später optimiert man bei Bedarf.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele
|
||||
|
||||
### 6.1 Minimalbeispiel: Flask-App hinter nginx + Gunicorn
|
||||
|
||||
**1. Eine einfache Flask-App (Python)**
|
||||
|
||||
Datei: `app.py`
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hallo, Welt! Das kommt aus Flask hinter nginx."
|
||||
```
|
||||
|
||||
Start des App-Servers (Gunicorn):
|
||||
|
||||
```bash
|
||||
pip install flask gunicorn
|
||||
gunicorn -w 3 -b 127.0.0.1:8000 app:app
|
||||
# -w 3: 3 Worker
|
||||
# -b 127.0.0.1:8000: lauscht auf Port 8000 nur lokal
|
||||
```
|
||||
|
||||
**2. nginx-Konfiguration (vereinfacht)**
|
||||
|
||||
Datei: z.B. `/etc/nginx/sites-available/myflaskapp`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com; # deine Domain oder IP
|
||||
|
||||
# Statische Dateien (wenn du welche hast)
|
||||
# root /var/www/myflaskapp; # falls du HTML/Assets direkt ablegen willst
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000; # Gunicorn
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Ablauf:
|
||||
|
||||
1. Browser ruft `http://example.com/` auf
|
||||
2. nginx nimmt Anfrage auf Port 80 entgegen
|
||||
3. nginx leitet an `http://127.0.0.1:8000` weiter (Gunicorn/Flask)
|
||||
4. Flask antwortet mit „Hallo, Welt“
|
||||
5. nginx sendet diese Antwort an den Browser zurück
|
||||
|
||||
|
||||
### 6.2 Beispiel: Django mit statischen Dateien
|
||||
|
||||
**1. Django-Setup (stark vereinfacht)**
|
||||
|
||||
In `settings.py`:
|
||||
|
||||
```python
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "/var/www/mydjangoproject/static/"
|
||||
```
|
||||
|
||||
Danach statische Dateien sammeln:
|
||||
|
||||
```bash
|
||||
python manage.py collectstatic
|
||||
```
|
||||
|
||||
**2. nginx-Konfiguration (Ausschnitt)**
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
# Statische Dateien direkt von nginx
|
||||
location /static/ {
|
||||
alias /var/www/mydjangoproject/static/;
|
||||
access_log off;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# Alles andere an Django via Gunicorn
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001; # hier läuft Gunicorn
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Vorteil:
|
||||
|
||||
- statische Inhalte kommen extrem schnell von nginx
|
||||
- Django/Gunicorn verarbeitet nur „echte“ App-Logik
|
||||
|
||||
|
||||
### 6.3 Beispiel: HTTPS mit Let’s Encrypt (Konzept)
|
||||
|
||||
Mit Tools wie **certbot** kannst du für nginx kostenlos TLS-Zertifikate holen.
|
||||
|
||||
Grobe Schritte:
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d example.com
|
||||
```
|
||||
|
||||
certbot:
|
||||
|
||||
- holt Zertifikat
|
||||
- passt nginx-Konfiguration an (Port 443, SSL-Parameter)
|
||||
- richtet automatische Erneuerung ein
|
||||
|
||||
nginx-Konfiguration enthält danach z.B.:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Damit ist deine Python-App per HTTPS erreichbar.
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusammenfassung
|
||||
|
||||
- **nginx** ist ein leistungsfähiger Webserver und Reverse Proxy, der im Python-Kontext typischerweise:
|
||||
- vor deiner Python-Webanwendung sitzt
|
||||
- HTTP(S) abwickelt
|
||||
- statische Dateien effizient ausliefert
|
||||
- Anfragen an einen Python-Application-Server (Gunicorn, uWSGI, uvicorn) weiterleitet
|
||||
- TLS/HTTPS, Load Balancing, Security-Features bereitstellt
|
||||
|
||||
- **Abgrenzung**:
|
||||
- nginx ≠ Python-Framework (Django/Flask/[[FastAPI]])
|
||||
- nginx ≠ Application-Server (Gunicorn/uvicorn)
|
||||
- nginx = Webserver/Reverse Proxy
|
||||
|
||||
- **Gelöste Probleme**:
|
||||
- sichere Produktion anstelle von Entwicklungsservern
|
||||
- Performance für statische Inhalte
|
||||
- TLS/HTTPS, Lastverteilung, Schutz der App
|
||||
|
||||
- **Herausforderungen**:
|
||||
- Konfigurationsdateien verstehen
|
||||
- Rechte & Pfade korrekt setzen
|
||||
- Fehler systematisch debuggen
|
||||
- bei hoher Last Performance tunen
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
Im Folgenden bekommst du eine Einführung in OAuth 2, so dass du danach:
|
||||
|
||||
- weißt, was OAuth 2 grundsätzlich ist,
|
||||
- es von verwandten Konzepten wie „Login mit Google“, OpenID Connect oder reiner Authentifizierung unterscheiden kannst,
|
||||
- verstehst, welche Probleme OAuth 2 löst – und welche neuen Herausforderungen es mit sich bringt,
|
||||
- und ein paar praxisnahe Szenarien im Kopf hast.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundsätzliche Definition: Was ist OAuth 2?
|
||||
|
||||
**Kurze Definition**:
|
||||
OAuth 2 ist ein *Autorisierungs*-Framework. Es regelt, wie eine Anwendung (Client) **im Namen eines Benutzers** sicher auf Ressourcen (z. B. APIs, Daten) zugreifen kann, **ohne dass die Anwendung das Passwort des Benutzers kennen muss**.
|
||||
|
||||
Es geht also primär um:
|
||||
|
||||
- **Zugriffsrechte (Authorization)**
|
||||
- **Delegation**: „Ich (Nutzer) erlaube App X, auf meine Daten bei Dienst Y zuzugreifen.“
|
||||
|
||||
### Zentrales Prinzip
|
||||
|
||||
Statt:
|
||||
- Benutzer gibt sein Passwort direkt bei jeder Drittanbieter-App ein
|
||||
|
||||
nutzt man:
|
||||
- Benutzer loggt sich beim **vertrauenswürdigen Dienst** ein (z. B. Google, GitHub, dein Unternehmens-Identity-Server)
|
||||
- Dieser Dienst stellt ein **Token** aus (eine Art Eintrittskarte)
|
||||
- Drittanbieter-App nutzt dieses Token, um beim API-Server Zugriff zu bekommen
|
||||
|
||||
Dadurch muss die Drittanbieter-App nie das Passwort sehen – sie bekommt **nur einen begrenzten, kontrollierten Zugriff**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Die wichtigsten Rollen in OAuth 2
|
||||
|
||||
Um Beispiele zu verstehen, braucht man die Standardrollen:
|
||||
|
||||
1. **Resource Owner**
|
||||
Der Besitzer der Daten/Ressourcen – meist der **Benutzer**.
|
||||
|
||||
2. **Client**
|
||||
Die Anwendung, die im Namen des Benutzers zugreifen möchte
|
||||
(z. B. eine mobile App, eine Web-App, ein Backend-Service).
|
||||
|
||||
3. **Resource Server**
|
||||
Die API/das System, das die geschützten Ressourcen bereitstellt
|
||||
(z. B. Google Calendar API, GitHub API, Unternehmens-REST-API).
|
||||
|
||||
4. **Authorization Server**
|
||||
Der Server, der:
|
||||
- den Benutzer authentifiziert (z. B. Login-Formular) und
|
||||
- **Tokens** ausstellt (Access Tokens, optional Refresh Tokens).
|
||||
|
||||
Oft sind Authorization Server und Resource Server derselbe technische Dienst, aber **konzeptionell** werden sie getrennt betrachtet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Grund-Mechanik: Wie läuft ein OAuth-2-Flow ab?
|
||||
|
||||
Stark vereinfacht am typischen Web-Szenario:
|
||||
|
||||
1. Benutzer nutzt eine Drittanbieter-App (Client) und klickt z. B. auf:
|
||||
„Verbinde dich mit meinem Google-Kalender“.
|
||||
|
||||
2. Client leitet den Browser zum **Authorization Server** (z. B. accounts.google.com) weiter:
|
||||
- mit Angaben wie: „Diese App möchte Zugriff auf Kalenderdaten“.
|
||||
|
||||
3. Benutzer loggt sich **beim Authorization Server** ein (falls nicht schon eingeloggt).
|
||||
|
||||
4. Benutzer bekommt eine Seite angezeigt:
|
||||
„Die Anwendung X möchte auf Ihre Kalenderdaten zugreifen. Erlauben? (Ja/Nein)“
|
||||
|
||||
5. Wenn Benutzer zustimmt:
|
||||
- Authorization Server schickt den Benutzer zurück zur App
|
||||
- Die App bekommt einen **Authorization Code** (bei einem gängigen Flow).
|
||||
|
||||
6. Die App tauscht diesen Code im Hintergrund beim Authorization Server gegen ein **Access Token** (und evtl. ein **Refresh Token**) ein.
|
||||
|
||||
7. Die App nutzt das Access Token, um beim **Resource Server** (z. B. Google Calendar API) Daten abzufragen.
|
||||
|
||||
Der Benutzer muss sein **Passwort nur beim Authorization Server** eingeben – nicht bei der Drittanbieter-App.
|
||||
|
||||
---
|
||||
|
||||
## 4. Abgrenzung zu ähnlichen / verwandten Begriffen
|
||||
|
||||
### 4.1 OAuth 2 (Authorization) vs. Authentifizierung (Login)
|
||||
|
||||
**Authentifizierung**: „Wer bist du?“
|
||||
**Autorisierung**: „Worauf darfst du zugreifen?“
|
||||
|
||||
- OAuth 2 löst formal **Autorisierung** (Delegation von Zugriffsrechten).
|
||||
- Viele Systeme nutzen OAuth 2 aber **im Zusammenhang mit Login**, was verwirrend sein kann.
|
||||
|
||||
Ein Login mit Google, bei dem eine Anwendung am Ende weiß „dieser User ist Max Mustermann“, basiert meist auf **OpenID Connect**, das **auf OAuth 2 aufsetzt**.
|
||||
|
||||
### 4.2 OAuth 2 vs. OpenID Connect (OIDC)
|
||||
|
||||
- **OAuth 2**: Spezifiziert, wie Clients **Access Tokens** bekommen, um auf APIs zuzugreifen.
|
||||
- **OpenID Connect**: Erweiterung auf Basis von OAuth 2, um **Identität** zu transportieren.
|
||||
|
||||
OIDC liefert zusätzlich:
|
||||
- ein **ID Token** (meist ein [[JWT token]]), das Informationen wie:
|
||||
- User-ID
|
||||
- E-Mail
|
||||
- Name
|
||||
enthält.
|
||||
|
||||
Praxis-Beispiele:
|
||||
|
||||
- „Mit Google anmelden“ (Login) → **OpenID Connect**
|
||||
- „Google Kalender-API nutzen“ → **OAuth 2** (Autorisierung für API-Zugriff)
|
||||
|
||||
### 4.3 OAuth 2 vs. API-Key
|
||||
|
||||
**API-Key**:
|
||||
- Meist ein statischer Schlüssel, der im Client eingebaut ist.
|
||||
- Unterscheidet nicht zwischen verschiedenen Benutzern.
|
||||
- Hat wenig feingranulare Rechte (oft: „alles oder nichts“).
|
||||
- Sicherheitsrisiko bei Leaks (z. B. im Frontend-Code).
|
||||
|
||||
**OAuth 2**:
|
||||
- Nutzerbezogen (Access Token repräsentiert den Benutzer/das Delegationsrecht).
|
||||
- Token haben meist begrenzte Lebensdauer.
|
||||
- Zugriffsrechte (Scopes) sind feiner steuerbar.
|
||||
|
||||
### 4.4 OAuth 2 vs. SAML
|
||||
|
||||
- **SAML**: Älterer Standard, XML-basiert, häufig in Unternehmensumgebungen für SSO verwendet.
|
||||
- **OAuth 2 / OIDC**: JSON/HTTP-basiert, leichtergewichtig, stark im Web- und Mobile-Bereich verbreitet.
|
||||
|
||||
Grober Merksatz:
|
||||
- SAML: klassische Unternehmen, Single Sign-On im Browser, ältere Infrastruktur.
|
||||
- OAuth 2 + OIDC: moderne Web-/Mobile-Apps, REST-APIs, Microservices.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Probleme löst OAuth 2?
|
||||
|
||||
### 5.1 Vermeidung von Passwortweitergabe an Drittanbieter
|
||||
|
||||
Früher:
|
||||
Eine App wollte auf Gmail zugreifen => Benutzer musste **sein Gmail-Passwort** in dieser App eingeben.
|
||||
Risiken:
|
||||
- App könnte Passwort speichern oder missbrauchen.
|
||||
- Bei Passwortänderung müsste der Benutzer alles neu einrichten.
|
||||
- Anbieter (Google) hat keine Kontrolle, welche App das tut.
|
||||
|
||||
Mit OAuth 2:
|
||||
- Benutzer loggt sich nur bei Google ein.
|
||||
- Drittanbieter-App erhält nur ein **Token mit begrenzten Rechten**, kein Passwort.
|
||||
|
||||
**Praxisbeispiel**:
|
||||
Eine To-Do-App möchte deine Google Kalender-Einträge anzeigen:
|
||||
Du klickst „Mit Google verbinden“, wirst zu Google geleitet, bestätigst die Berechtigungen und die App sieht danach deine Kalender – ohne dein Passwort zu kennen.
|
||||
|
||||
### 5.2 Feingranulare Zugriffsrechte (Scopes)
|
||||
|
||||
Mit **Scopes** kann eingeschränkt werden, worauf eine App zugreifen darf.
|
||||
|
||||
Beispiele:
|
||||
- `read_calendar`: nur Leserechte auf Kalender
|
||||
- `write_calendar`: Schreibrechte auf Kalender
|
||||
- `email`: Zugriff auf deine E-Mail-Adresse
|
||||
- `profile`: Zugriff auf deine Basisprofildaten
|
||||
|
||||
So kann der Benutzer (und auch der Resource Server) klar kontrollieren:
|
||||
- Eine App darf z. B. **nur lesen**, nicht schreiben.
|
||||
- Eine App darf **nur auf E-Mail und Profil**, nicht auf Kontakte oder Drive zugreifen.
|
||||
|
||||
### 5.3 Zeitlich begrenzte Tokens
|
||||
|
||||
**Access Tokens** sind typischerweise kurzlebig (z. B. 5–60 Minuten).
|
||||
|
||||
- Wenn ein Token geleakt wird, ist der Schaden zeitlich begrenzt.
|
||||
- Über Revocation-Mechanismen kann der Zugriff vorzeitig beendet werden.
|
||||
|
||||
Optional gibt es **Refresh Tokens**:
|
||||
- Länger gültig
|
||||
- Dienen dazu, neue Access Tokens zu holen
|
||||
- Können vom Authorization Server gezielt deaktiviert werden (z. B. bei Kompromittierung).
|
||||
|
||||
### 5.4 Single Sign-On / bessere User Experience (in Kombination mit OIDC)
|
||||
|
||||
In Kombination mit OIDC und Single-Sign-On-Konzepten:
|
||||
- Benutzer loggt sich einmal beim zentralen Identity Provider ein.
|
||||
- Mehrere Anwendungen können dann in einer Session auf Tokens zugreifen, ohne den Benutzer jedes Mal neu einzuloggen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Typische OAuth-2-Flows (vereinfacht)
|
||||
|
||||
Es gibt verschiedene **„Flows“**, je nach Art des Clients und Nutzungsszenario. Die wichtigsten:
|
||||
|
||||
### 6.1 Authorization Code Flow (heute mit PKCE)
|
||||
|
||||
Typisch für:
|
||||
- Web-Anwendungen mit Backend
|
||||
- Single Page Applications (mit PKCE)
|
||||
- Mobile Apps
|
||||
|
||||
Ablauf (vereinfacht, ohne technische Details):
|
||||
|
||||
1. Anwendung leitet Benutzer zum Authorization Server.
|
||||
2. Benutzer loggt sich ein und stimmt zu.
|
||||
3. Authorization Server schickt **Authorization Code** an die Anwendung (über Redirect).
|
||||
4. Anwendung tauscht Code gegen ein **Access Token** (und optional Refresh Token).
|
||||
|
||||
**PKCE** (Proof Key for Code Exchange) ist eine Sicherheits-Erweiterung, um Code-Diebstahl zu verhindern, insbesondere bei mobilen/Browser-basierten Apps.
|
||||
|
||||
### 6.2 Client Credentials Flow
|
||||
|
||||
Für:
|
||||
- Machine-to-Machine-Kommunikation (kein Benutzer)
|
||||
- Hintergrundprozesse, Microservices, Server-zu-Server
|
||||
|
||||
Hier gibt es keinen Benutzer.
|
||||
Der Client identifiziert sich selbst (z. B. mit Client ID + Client Secret) beim Authorization Server und bekommt ein Access Token, um auf eine API zuzugreifen.
|
||||
|
||||
**Beispiel**:
|
||||
Ein Backend-Service ruft periodisch eine interne Unternehmens-API auf, um Daten zu synchronisieren.
|
||||
|
||||
### 6.3 Device Code Flow
|
||||
|
||||
Für Geräte ohne vollwertigen Browser oder ohne komfortable Eingabemöglichkeit, wie:
|
||||
- Smart-TVs
|
||||
- Konsolen
|
||||
- IoT-Geräte
|
||||
|
||||
Ablauf:
|
||||
- Gerät zeigt einen Code und eine URL an (z. B. „Bitte gehen Sie auf https://example.com/device und geben Sie Code ABCD ein“).
|
||||
- Benutzer geht an seinem Smartphone/PC auf diese URL, loggt sich dort ein und bestätigt.
|
||||
- Gerät bekommt anschließend ein Token und kann auf die Ressource zugreifen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Herausforderungen und typische Stolperfallen
|
||||
|
||||
### 7.1 OAuth 2 ist komplex und flexibel
|
||||
|
||||
OAuth 2 ist sehr flexibel – das ist Stärke und Schwäche zugleich:
|
||||
|
||||
- Viele Optionen & Erweiterungen → schnell unübersichtlich.
|
||||
- Viele Entscheidungen zu treffen: Flows, Token-Formate, Scopes, Lifetimes, PKCE, etc.
|
||||
|
||||
Für Einsteiger wirkt es oft „überdimensioniert“, vor allem wenn man nur „einen einfachen Login“ braucht (wo eigentlich OIDC gefragt ist).
|
||||
|
||||
### 7.2 Verwechslung von OAuth 2 und Login
|
||||
|
||||
Weil viele große Anbieter beides kombiniert haben („Login mit Google“), entsteht oft:
|
||||
|
||||
- „OAuth 2 ist ein Login-Protokoll.“ → **Formal falsch**, es ist ein Autorisierungsframework.
|
||||
- Für „Login“ verwendet man **OpenID Connect**, auch wenn technisch OAuth-2-Mechanismen verwendet werden.
|
||||
|
||||
Konsequenz:
|
||||
Fehlkonzeptionen in Projekten, z. B. versucht man reine OAuth-2-Tokens zu nutzen, um Identität abzuleiten, statt OIDC zu verwenden.
|
||||
|
||||
### 7.3 Sicherheitsrisiken bei falscher Implementierung
|
||||
|
||||
Einige typische Fehler:
|
||||
|
||||
- **Tokens in URLs weitergeben** → landen in Logs, Browser-History, Referrer-Headern.
|
||||
- **Implicit Flow** für Single-Page-Apps ohne ausreichende Sicherheitsmaßnahmen
|
||||
→ gilt inzwischen als überholt; empfohlen wird Authorization Code Flow mit PKCE.
|
||||
- **Keine Einschränkung der Redirect-URIs** → Angreifer können Redirect-URIs manipulieren und Tokens abfangen.
|
||||
- **Zu lange gültige Tokens** → höheres Risiko bei Leaks.
|
||||
- **Speichern von Tokens im unsicheren Speicher** (z. B. localStorage in Browsern) → anfällig für XSS.
|
||||
|
||||
### 7.4 Komplexität für Endnutzer
|
||||
|
||||
Auch für Endnutzer ist die Zustimmung manchmal nicht klar:
|
||||
|
||||
- „Diese App möchte auf Ihr Profil, Ihre Kontakte, Ihre Dateien, Ihre E-Mails zugreifen.“
|
||||
→ Viele klicken aus Gewohnheit auf „Zulassen“, ohne zu verstehen, was das bedeutet.
|
||||
|
||||
Das ist eher ein UX-/Privacy-Thema, aber eng mit OAuth 2 verbunden, da die Darstellung der **Scopes** oft technisch und verwirrend ist.
|
||||
|
||||
---
|
||||
|
||||
## 8. Praxisnahe Beispiele
|
||||
|
||||
### Beispiel 1: „Mit Google anmelden“ in einer Web-App
|
||||
|
||||
**Situation**:
|
||||
Du entwickelst eine Web-App, in der sich Nutzer anmelden können. Statt ein eigenes Benutzerkontensystem aufzubauen, willst du „Login mit Google“ anbieten.
|
||||
|
||||
**In Wahrheit passiert (typisch)**:
|
||||
- Die App nutzt **OpenID Connect**:
|
||||
- OAuth 2 regelt den Token-Flow.
|
||||
- OIDC liefert ein ID Token mit Infos über den Benutzer.
|
||||
- Nutzer sieht: Google-Login, eine Consent-Seite, und wird danach in deine App zurückgeleitet.
|
||||
- Deine App bekommt:
|
||||
- ein **ID Token** (zur Identifikation des Nutzers)
|
||||
- ein **Access Token** (um z. B. zusätzlich auf Google APIs zuzugreifen, falls gewünscht).
|
||||
|
||||
**OAuth-2-Aspekt**:
|
||||
Der eigentliche Mechanismus, wie Access Token ausgestellt werden, ist OAuth 2.
|
||||
|
||||
### Beispiel 2: Drittanbieter-Tool liest GitHub-Repositories
|
||||
|
||||
**Situation**:
|
||||
Ein Build-Tool oder eine CI/CD-Plattform möchte deine privaten GitHub-Repositories lesen, um Builds auszulösen.
|
||||
|
||||
Ablauf:
|
||||
1. In der CI/CD-Plattform klickst du „Mit GitHub verbinden“.
|
||||
2. Du wirst zu GitHub weitergeleitet, loggst dich ein.
|
||||
3. GitHub zeigt dir eine Seite:
|
||||
„Tool X möchte Zugriff auf:
|
||||
- Ihre öffentlichen und privaten Repositories
|
||||
- Ihre Webhooks
|
||||
…“
|
||||
4. Du erlaubst.
|
||||
5. Die CI/CD-Plattform erhält ein Access Token mit bestimmten **Scopes** (z. B. `repo`).
|
||||
|
||||
**OAuth-2-Aspekt**:
|
||||
- Das Access Token erlaubt der CI/CD-Plattform, im Namen deines GitHub-Accounts zu agieren, innerhalb des erlaubten Scopes.
|
||||
|
||||
### Beispiel 3: Unternehmensinternes Microservice-System
|
||||
|
||||
**Situation**:
|
||||
In einer Microservice-Architektur gibt es:
|
||||
- einen zentralen Authorization Server (z. B. Keycloak, Auth0, Azure AD),
|
||||
- viele Services, die interne APIs bereitstellen.
|
||||
|
||||
Szenario:
|
||||
- Ein Frontend bekommt vom Authorization Server (über einen Login) ein Access Token.
|
||||
- Dieses Access Token wird vom Frontend bei Aufrufen an das API-Gateway oder direkt an Microservices mitgeschickt.
|
||||
- Microservices verifizieren das Token und entscheiden anhand von Scopes/Rollen, ob der Zugriff erlaubt ist.
|
||||
|
||||
**OAuth-2-Aspekt**:
|
||||
- Zugriffskontrolle wird zentralisiert.
|
||||
- Jeder Service muss nicht mehr selbst Benutzer verwalten, sondern nur Access Tokens prüfen.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zusammenfassung in Stichpunkten
|
||||
|
||||
- **OAuth 2** ist ein **Autorisierungs-Framework** (nicht direkt ein Login-Protokoll).
|
||||
- Es ermöglicht, dass **Drittanbieter-Apps im Namen eines Nutzers** auf Ressourcen zugreifen können, ohne das Passwort des Nutzers zu kennen.
|
||||
- Wichtige Rollen: **Resource Owner**, **Client**, **Resource Server**, **Authorization Server**.
|
||||
- Es arbeitet mit **Tokens** (Access Tokens, optional Refresh Tokens) und **Scopes** (feingranulare Rechte).
|
||||
- Verwandte Standards:
|
||||
- **OpenID Connect**: für Authentifizierung/Identität (baut auf OAuth 2 auf).
|
||||
- **SAML**: alternative, ältere Lösung v. a. für SSO in Unternehmen.
|
||||
- Es löst Probleme wie:
|
||||
- Passwortweitergabe an Drittanbieter
|
||||
- Fehlende Feingranularität von Zugriffsrechten
|
||||
- Mangelnde Kontrolle über Zugriffsdelegation
|
||||
- Herausforderungen:
|
||||
- Komplexität und viele Optionen
|
||||
- Verwechslungsgefahr mit Authentifizierung
|
||||
- Sicherheitsrisiken bei falscher Implementierung
|
||||
- UX/Verständlichkeit von Zustimmungsdialogen
|
||||
- Praxis:
|
||||
- „Mit Google/GitHub/Facebook anmelden“
|
||||
- Drittanbieter-Apps, die auf APIs zugreifen
|
||||
- Microservice-Systeme mit zentralem Identity & Access Management
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
#infrastructure #Cloud
|
||||
## 1. Was ist Terraform?
|
||||
|
||||
Terraform ist ein Open-Source-Tool (ursprünglich MIT, seit 2023 Business Source License, BSL) von HashiCorp zur **deklarativen** Beschreibung und Verwaltung von Infrastruktur als Code (Infrastructure as Code, IaC).
|
||||
|
||||
Mit Terraform kannst du:
|
||||
|
||||
- Infrastruktur in Cloud-Providern (AWS, Azure, GCP, …), On-Premise und weiteren Systemen **beschreiben** (als Code).
|
||||
- Aus dieser Beschreibung eine **gewünschte Zielkonfiguration** (Desired State) definieren.
|
||||
- Terraform berechnet dann, **welche Änderungen nötig sind**, um vom Ist-Zustand zum Soll-Zustand zu gelangen.
|
||||
- Diese Änderungen werden mit `terraform apply` **automatisch ausgeführt**.
|
||||
|
||||
Terraform ist besonders stark in:
|
||||
|
||||
- **Multi-Cloud- und Hybrid-Szenarien**
|
||||
- **Reproduzierbaren Umgebungen** (z.B. Dev, Test, Prod)
|
||||
- **Versionierbarer Infrastruktur** (Git)
|
||||
- **Team-Kollaboration** an Infrastruktur
|
||||
|
||||
---
|
||||
|
||||
## 2. Grundkonzepte von Terraform
|
||||
|
||||
Die wichtigsten Bausteine:
|
||||
|
||||
### 2.1 Provider
|
||||
|
||||
Provider sind die „Treiber“, mit denen Terraform mit einem externen System spricht, z.B.:
|
||||
|
||||
- `aws` – Amazon Web Services
|
||||
- `azurerm` – Microsoft Azure
|
||||
- `google` – Google Cloud
|
||||
- `kubernetes` – Kubernetes-Cluster
|
||||
- `helm`, `docker`, `vault`, `github`, viele weitere
|
||||
|
||||
In Terraform-Code bindet man einen Provider ein, z.B.:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = "eu-central-1"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Ressourcen
|
||||
|
||||
**Ressourcen** (`resource`) sind die eigentlichen Infrastruktur-Objekte, z.B.:
|
||||
|
||||
- `aws_instance` (EC2-VM)
|
||||
- `aws_s3_bucket`
|
||||
- `azurerm_resource_group`
|
||||
- `google_compute_instance`
|
||||
|
||||
Beispiel:
|
||||
|
||||
```hcl
|
||||
resource "aws_s3_bucket" "example" {
|
||||
bucket = "mein-terraform-bucket-12345"
|
||||
acl = "private"
|
||||
}
|
||||
```
|
||||
|
||||
`aws_s3_bucket` ist der Ressourcentyp, `"example"` der Name innerhalb der Terraform-Konfiguration.
|
||||
|
||||
### 2.3 Datenquellen (Data Sources)
|
||||
|
||||
`data`-Blöcke lesen Informationen aus einer bestehenden Umgebung, ohne etwas zu verändern.
|
||||
|
||||
```hcl
|
||||
data "aws_ami" "ubuntu" {
|
||||
most_recent = true
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
|
||||
}
|
||||
|
||||
owners = ["099720109477"] # Canonical
|
||||
}
|
||||
```
|
||||
|
||||
Die Daten können dann in Ressourcen verwendet werden.
|
||||
|
||||
### 2.4 Variablen und Outputs
|
||||
|
||||
**Variablen** (`variable`) erlauben Parametrisierung:
|
||||
|
||||
```hcl
|
||||
variable "region" {
|
||||
type = string
|
||||
default = "eu-central-1"
|
||||
description = "AWS Region"
|
||||
}
|
||||
```
|
||||
|
||||
Aufgerufen z.B.: `var.region`.
|
||||
|
||||
**Outputs** (`output`) geben wichtige Informationen aus (z.B. IP-Adressen):
|
||||
|
||||
```hcl
|
||||
output "instance_ip" {
|
||||
value = aws_instance.web.public_ip
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 Module
|
||||
|
||||
Module sind wiederverwendbare Bausteine von Terraform-Konfigurationen (eine Art „Bibliothek“ von Infrastruktur).
|
||||
|
||||
- Ein Modul ist einfach ein Ordner mit `.tf`-Dateien.
|
||||
- Man kann eigene Module oder Community-Module nutzen (z.B. aus dem Terraform Registry).
|
||||
|
||||
Beispiel Nutzung eines Moduls:
|
||||
|
||||
```hcl
|
||||
module "network" {
|
||||
source = "./modules/network"
|
||||
|
||||
vpc_cidr = "10.0.0.0/16"
|
||||
}
|
||||
```
|
||||
|
||||
Oder:
|
||||
|
||||
```hcl
|
||||
module "vpc" {
|
||||
source = "terraform-aws-modules/vpc/aws"
|
||||
version = "5.0.0"
|
||||
|
||||
name = "main-vpc"
|
||||
cidr = "10.0.0.0/16"
|
||||
azs = ["eu-central-1a", "eu-central-1b"]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 Terraform State
|
||||
|
||||
Terraform hält den Zustand der verwalteten Ressourcen in einer **State-Datei** (`terraform.tfstate`) fest.
|
||||
|
||||
- Der State ist die „Wahrheit“, mit der Terraform den Ist-Zustand kennt.
|
||||
- Daran erkennt Terraform, welche Ressource es schon erstellt hat und wie deren IDs, Attribute usw. sind.
|
||||
- Änderungen am Code werden mit dem State abgeglichen → Terraform generiert einen Plan.
|
||||
|
||||
State kann liegen:
|
||||
|
||||
- lokal: `terraform.tfstate` im Projektordner
|
||||
- remote (empfohlen für Teams): z.B. in S3, Azure Storage, GCS, Terraform Cloud, etc.
|
||||
|
||||
Beispiel Backend-Konfiguration (Remote State mit S3):
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "my-terraform-state-bucket"
|
||||
key = "prod/terraform.tfstate"
|
||||
region = "eu-central-1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Typischer Terraform-Workflow
|
||||
|
||||
### 3.1 Projektstruktur
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
.
|
||||
├── main.tf # Ressourcen, Module
|
||||
├── variables.tf # Variablen
|
||||
├── outputs.tf # Outputs
|
||||
└── providers.tf # Provider / Backend
|
||||
```
|
||||
|
||||
### 3.2 Wichtige Terraform-Kommandos
|
||||
|
||||
1. `terraform init`
|
||||
- Initialisiert das Projekt
|
||||
- Lädt Provider-Plugins
|
||||
- Konfiguriert Backend (State)
|
||||
|
||||
2. `terraform plan`
|
||||
- Zeigt, was Terraform ändern würde (Create/Update/Delete)
|
||||
- Verändert noch nichts
|
||||
- Wichtig für Review (z.B. im CI)
|
||||
|
||||
3. `terraform apply`
|
||||
- Führt den Plan aus (Standard: vorher Anzeige + Bestätigung)
|
||||
- Erzeugt/aktualisiert/löscht Ressourcen
|
||||
|
||||
4. `terraform destroy`
|
||||
- Löscht alle Ressourcen, die Terraform verwaltet
|
||||
- Vorsicht: de facto „Infrastruktur-Abbau“
|
||||
|
||||
Typischer Ablauf:
|
||||
|
||||
```bash
|
||||
terraform init
|
||||
terraform plan
|
||||
terraform apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Terraform-Sprache: HCL (HashiCorp Configuration Language)
|
||||
|
||||
Terraform benutzt HCL, eine deklarative, blockbasierte Sprache.
|
||||
|
||||
### 4.1 Syntax-Grundlagen
|
||||
|
||||
Blöcke haben Form:
|
||||
|
||||
```hcl
|
||||
resource "TYP" "NAME" {
|
||||
argument = "value"
|
||||
|
||||
block {
|
||||
nested_arg = 123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Datentypen:
|
||||
|
||||
- `string` – `"Text"`
|
||||
- `number` – `42`
|
||||
- `bool` – `true` / `false`
|
||||
- Listen – `["a", "b"]`
|
||||
- Maps – `{ key = "value" }`
|
||||
- Objekte/Tuples – komplexere Typen
|
||||
|
||||
### 4.2 Expressions & Referenzen
|
||||
|
||||
Referenzen auf andere Ressourcen:
|
||||
|
||||
```hcl
|
||||
resource "aws_instance" "web" {
|
||||
ami = data.aws_ami.ubuntu.id
|
||||
instance_type = var.instance_type
|
||||
|
||||
tags = {
|
||||
Name = "web-${var.environment}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Referenz-Regel:
|
||||
|
||||
- `ressourcentyp.name.attribut`
|
||||
- z.B. `aws_instance.web.public_ip`
|
||||
|
||||
---
|
||||
|
||||
## 5. Praktisches Beispiel
|
||||
|
||||
Ein minimaler Stack in AWS:
|
||||
|
||||
### `providers.tf`
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
}
|
||||
```
|
||||
|
||||
### `variables.tf`
|
||||
|
||||
```hcl
|
||||
variable "region" {
|
||||
type = string
|
||||
default = "eu-central-1"
|
||||
description = "AWS Region"
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
type = string
|
||||
default = "t3.micro"
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
type = string
|
||||
default = "dev"
|
||||
}
|
||||
```
|
||||
|
||||
### `main.tf`
|
||||
|
||||
```hcl
|
||||
data "aws_ami" "ubuntu" {
|
||||
most_recent = true
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
|
||||
}
|
||||
|
||||
owners = ["099720109477"]
|
||||
}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
ami = data.aws_ami.ubuntu.id
|
||||
instance_type = var.instance_type
|
||||
|
||||
tags = {
|
||||
Name = "web-${var.environment}"
|
||||
Environment = var.environment
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `outputs.tf`
|
||||
|
||||
```hcl
|
||||
output "web_instance_id" {
|
||||
value = aws_instance.web.id
|
||||
}
|
||||
|
||||
output "web_instance_ami" {
|
||||
value = aws_instance.web.ami
|
||||
}
|
||||
```
|
||||
|
||||
Ablauf:
|
||||
|
||||
```bash
|
||||
terraform init
|
||||
terraform plan
|
||||
terraform apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Workspaces, Environments und Modularisierung
|
||||
|
||||
### 6.1 Workspaces
|
||||
|
||||
Terraform Workspaces ermöglichen getrennte States innerhalb desselben Codes (z.B. `default`, `dev`, `prod`).
|
||||
|
||||
```bash
|
||||
terraform workspace list
|
||||
terraform workspace new dev
|
||||
terraform workspace select dev
|
||||
```
|
||||
|
||||
Dann z.B.:
|
||||
|
||||
```hcl
|
||||
variable "environment" {
|
||||
default = terraform.workspace
|
||||
}
|
||||
```
|
||||
|
||||
Viele Teams gehen aber eher über **separate State-Dateien / Ordner / Repos** (z.B. `envs/dev`, `envs/prod`), statt Workspaces intensiv zu nutzen.
|
||||
|
||||
### 6.2 Module für Wiederverwendung
|
||||
|
||||
Struktur-Beispiel:
|
||||
|
||||
```text
|
||||
.
|
||||
├── main.tf
|
||||
├── modules
|
||||
│ ├── network
|
||||
│ │ ├── main.tf
|
||||
│ │ ├── variables.tf
|
||||
│ │ └── outputs.tf
|
||||
│ └── compute
|
||||
│ ├── main.tf
|
||||
│ ├── variables.tf
|
||||
│ └── outputs.tf
|
||||
```
|
||||
|
||||
In `main.tf`:
|
||||
|
||||
```hcl
|
||||
module "network" {
|
||||
source = "./modules/network"
|
||||
vpc_cidr = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
module "compute" {
|
||||
source = "./modules/compute"
|
||||
vpc_id = module.network.vpc_id
|
||||
subnets = module.network.subnet_ids
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Terraform Cloud / Terraform Enterprise
|
||||
|
||||
Neben der CLI gibt es:
|
||||
|
||||
- **Terraform Cloud** (SaaS von HashiCorp)
|
||||
- **Terraform Enterprise** (Self-hosted)
|
||||
|
||||
Features:
|
||||
|
||||
- Remote-State-Storage
|
||||
- Remote-Execution (Plans/Applies)
|
||||
- Policies (Sentinel)
|
||||
- Team & Governance (RBAC)
|
||||
- UI für Runs, Logs, States, Variablen
|
||||
|
||||
Für kleine Teams reicht oft: Git + Remote State (S3/Azure/GCS) + CI/CD Pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 8. Typische Anwendungsfälle
|
||||
|
||||
- **Cloud-Infrastruktur**:
|
||||
- VPCs/Netzwerke, Subnets, Security Groups
|
||||
- Compute (VMs, Managed Kubernetes, Serverless)
|
||||
- Datenbanken, Caches, Messaging
|
||||
- **Kubernetes-Infrastruktur**:
|
||||
- Cluster (EKS/AKS/GKE)
|
||||
- Add-ons via `kubernetes`-Provider, `helm`-Provider
|
||||
- **Multi-Cloud-Szenarien**:
|
||||
- Einheitliches Tool für AWS + Azure + GCP
|
||||
- **On-Prem / Sonstiges**:
|
||||
- VMware, OpenStack, F5, GitHub-Repos, DNS (Cloudflare/Route53), Monitoring (Datadog, New Relic), etc.
|
||||
|
||||
---
|
||||
|
||||
## 9. Vergleich zu anderen Tools
|
||||
|
||||
### Terraform vs. CloudFormation / ARM / Bicep
|
||||
|
||||
- Terraform:
|
||||
- Provider-übergreifend (nicht auf eine Cloud beschränkt)
|
||||
- HCL, verständliche Syntax
|
||||
- CloudFormation (AWS) / ARM/Bicep (Azure) / Deployment Manager (GCP):
|
||||
- Cloud-spezifisch, tief integriert
|
||||
- Gut für reines Single-Cloud-Setup
|
||||
- Multi-Cloud wird komplex
|
||||
|
||||
### Terraform vs. Ansible / Chef / Puppet
|
||||
|
||||
- Terraform:
|
||||
- Fokus: **Provisioning** + Lebenszyklus von Ressourcen
|
||||
- Deklarativ, fokus auf Infrastruktur
|
||||
- Ansible/Chef/Puppet:
|
||||
- Fokus: **Konfiguration** von Servern / Software
|
||||
- IdR. auf bereits existierende Maschinen
|
||||
|
||||
Häufig werden Terraform + Ansible kombiniert:
|
||||
Terraform baut VMs / Netzwerke, Ansible konfiguriert Software im OS.
|
||||
|
||||
### Terraform vs. Pulumi
|
||||
|
||||
- Pulumi: IaC mit **allgemeinen Programmiersprachen** (TypeScript, Python, Go, C#)
|
||||
- Terraform: HCL, deklarativ, stark verbreitete Community
|
||||
- Pulumi bietet mehr „Programmier-Features“ (Loops, Ifs) direkt in Sprache; Terraform hat ähnliche Mechanismen, aber bewusst begrenzt, um die Konfiguration simpel zu halten.
|
||||
|
||||
---
|
||||
|
||||
## 10. Best Practices & Stolperfallen
|
||||
|
||||
### 10.1 Best Practices
|
||||
|
||||
- **State nicht lokal** speichern, sondern Remote (z.B. S3 + DynamoDB Locking, Azure Storage + Locks).
|
||||
- **State schützen**:
|
||||
- Zugriff beschränken (IAM/RBAC)
|
||||
- Backups
|
||||
- **Struktur / Modularisierung**:
|
||||
- Wiederverwendbare Module
|
||||
- Trennung von Environments (dev/stage/prod)
|
||||
- **Git-Workflow**:
|
||||
- Terraform-Code in Repos
|
||||
- PRs/Merge-Requests
|
||||
- `terraform plan` via CI im PR anzeigen
|
||||
- **Versionen pinnen**:
|
||||
- Terraform-Version `required_version`
|
||||
- Provider-Versionen fixieren (z.B. `~> 5.0`)
|
||||
- **Kleine, inkrementelle Änderungen**:
|
||||
- Große Umbauten in mehreren Schritten
|
||||
- Plan gut reviewen
|
||||
|
||||
### 10.2 Typische Probleme
|
||||
|
||||
- **State-Drift**:
|
||||
- Ressourcen werden außerhalb von Terraform geändert (z.B. in der Cloud-Konsole)
|
||||
- Plan zeigt unerwartete Änderungen
|
||||
- Lösung: soweit möglich alle Änderungen via Terraform; bei Drift bewusst entscheiden (import, adopt, ignore)
|
||||
- **Manuelles Löschen von Ressourcen**:
|
||||
- Terraform „denkt“, Ressource existiert noch
|
||||
- Beim nächsten Apply werden sie ggf. neu erstellt
|
||||
- **Zyklen in Abhängigkeiten**:
|
||||
- Falsche Referenzen können zyklische Abhängigkeiten erzeugen
|
||||
- **Große States**:
|
||||
- Sehr viele Ressourcen in einem State → langsamere Plans, unübersichtliche Fehler
|
||||
- Lösung: Aufteilen in mehrere Terraform-Projekte / States (z.B. pro Domäne/Layer)
|
||||
|
||||
---
|
||||
|
||||
## 11. Aktuelle Entwicklungen: Lizenz & OpenTofu
|
||||
|
||||
HashiCorp hat 2023 die Lizenz von Terraform auf die **Business Source License (BSL)** geändert.
|
||||
Daraufhin entstand ein **Community-Fork**:
|
||||
|
||||
- **OpenTofu** (ehemals OpenTF):
|
||||
- Komplett Open Source (MPL 2.0)
|
||||
- CLI & HCL weitgehend kompatibel zu Terraform (Stand heute)
|
||||
- Ziel: drop-in Replacement für viele Terraform-Usecases
|
||||
|
||||
Für einen Einstieg in IaC ist es aber sinnvoll, zuerst die Konzepte anhand von Terraform zu verstehen – die meisten davon sind für OpenTofu nahezu identisch.
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
Hier ist eine kompakte, praxisorientierte Anleitung zu `kubectl` mit den wichtigsten Funktionen und Use Cases.
|
||||
|
||||
---
|
||||
|
||||
## 1. Was ist `kubectl`?
|
||||
|
||||
`kubectl` ist das Kommandozeilen-Tool, um mit einem Kubernetes-Cluster zu kommunizieren.
|
||||
Damit kannst du:
|
||||
|
||||
- Ressourcen erstellen, ändern und löschen (Deployments, Pods, Services, …)
|
||||
- Logs einsehen und Probleme debuggen
|
||||
- Rollouts steuern (Deployments updaten/rollback)
|
||||
- Konfigurationen verwalten (Kontexte, Namespaces, Kubeconfig)
|
||||
|
||||
---
|
||||
|
||||
## 2. Grundlagen: Aufbau von `kubectl`-Befehlen
|
||||
|
||||
Allgemeines Schema:
|
||||
|
||||
```bash
|
||||
kubectl <verb> <ressource> [name] [flags]
|
||||
```
|
||||
|
||||
Beispiele:
|
||||
|
||||
```bash
|
||||
kubectl get pods
|
||||
kubectl describe pod mein-pod
|
||||
kubectl delete service mein-service
|
||||
```
|
||||
|
||||
Wichtige Verben:
|
||||
|
||||
- `get` – Anzeigen
|
||||
- `describe` – Detailinformationen
|
||||
- `create` / `apply` – Ressourcen anlegen/aktualisieren
|
||||
- `delete` – Löschen
|
||||
- `logs` – Logs anzeigen
|
||||
- `exec` – Befehle innerhalb eines Pods ausführen
|
||||
|
||||
---
|
||||
|
||||
## 3. Konfiguration & Kontexte
|
||||
|
||||
### 3.1 Cluster-Zugriff prüfen
|
||||
|
||||
```bash
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
```
|
||||
|
||||
### 3.2 Aktuellen Kontext anzeigen und wechseln
|
||||
|
||||
```bash
|
||||
kubectl config get-contexts
|
||||
kubectl config current-context
|
||||
kubectl config use-context mein-context
|
||||
```
|
||||
|
||||
### 3.3 Namespace festlegen
|
||||
|
||||
Temporär per Flag:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n my-namespace
|
||||
```
|
||||
|
||||
Standard-Namespace setzen:
|
||||
|
||||
```bash
|
||||
kubectl config set-context --current --namespace=my-namespace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Ressourcen anzeigen und inspizieren
|
||||
|
||||
### 4.1 Ressourcen auflisten
|
||||
|
||||
```bash
|
||||
kubectl get pods
|
||||
kubectl get deployments
|
||||
kubectl get services
|
||||
kubectl get all
|
||||
```
|
||||
|
||||
Mit mehr Details:
|
||||
|
||||
```bash
|
||||
kubectl get pods -o wide
|
||||
kubectl get pods -o yaml
|
||||
```
|
||||
|
||||
### 4.2 Detailinformationen zu einer Ressource
|
||||
|
||||
```bash
|
||||
kubectl describe pod mein-pod
|
||||
kubectl describe deployment mein-deployment
|
||||
```
|
||||
|
||||
Use Case: Debugging (Events, Container-Status, Restart-Gründe).
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployments & Anwendungen verwalten
|
||||
|
||||
### 5.1 Deployment aus YAML erstellen
|
||||
|
||||
`deployment.yaml` Beispiel (stark vereinfacht):
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.25
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
Anwenden:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
```
|
||||
|
||||
### 5.2 Deployment aktualisieren (Rolling Update)
|
||||
|
||||
Image ändern:
|
||||
|
||||
```bash
|
||||
kubectl set image deployment/nginx-deployment nginx=nginx:1.26
|
||||
```
|
||||
|
||||
Rollout-Status beobachten:
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
Rollback:
|
||||
|
||||
```bash
|
||||
kubectl rollout undo deployment/nginx-deployment
|
||||
```
|
||||
|
||||
### 5.3 Replica-Anzahl skalieren
|
||||
|
||||
```bash
|
||||
kubectl scale deployment nginx-deployment --replicas=5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Services & Zugriff auf Anwendungen
|
||||
|
||||
### 6.1 Services anzeigen
|
||||
|
||||
```bash
|
||||
kubectl get svc
|
||||
```
|
||||
|
||||
Beispiel-Service:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-service
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: nginx
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
```
|
||||
|
||||
Anwenden:
|
||||
|
||||
```bash
|
||||
kubectl apply -f service.yaml
|
||||
```
|
||||
|
||||
### 6.2 Port-Forwarding (lokaler Zugriff)
|
||||
|
||||
```bash
|
||||
kubectl port-forward svc/nginx-service 8080:80
|
||||
# Aufruf im Browser: http://localhost:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Logs & Debugging
|
||||
|
||||
### 7.1 Logs eines Pods
|
||||
|
||||
```bash
|
||||
kubectl logs mein-pod
|
||||
kubectl logs mein-pod -c container-name
|
||||
kubectl logs -f mein-pod # -f = "follow"
|
||||
```
|
||||
|
||||
### 7.2 Befehle im Container ausführen
|
||||
|
||||
```bash
|
||||
kubectl exec -it mein-pod -- /bin/sh
|
||||
# oder
|
||||
kubectl exec -it mein-pod -- bash
|
||||
```
|
||||
|
||||
Typischer Use Case: Debugging von laufenden Containern, z.B. Netzwerkprobleme, Dateisystem prüfen.
|
||||
|
||||
### 7.3 Problem-Analyse mit `describe` & Events
|
||||
|
||||
```bash
|
||||
kubectl describe pod mein-pod
|
||||
kubectl get events --sort-by=.metadata.creationTimestamp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Ressourcen ändern & löschen
|
||||
|
||||
### 8.1 Änderungen anwenden
|
||||
|
||||
Konfiguration in einer Datei ändern und dann:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
```
|
||||
|
||||
Um tatsächliche Unterschiede zu sehen:
|
||||
|
||||
```bash
|
||||
kubectl diff -f deployment.yaml
|
||||
```
|
||||
|
||||
### 8.2 Ressourcen löschen
|
||||
|
||||
```bash
|
||||
kubectl delete pod mein-pod
|
||||
kubectl delete -f deployment.yaml
|
||||
kubectl delete deployment nginx-deployment
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Konfigurationen & Secrets
|
||||
|
||||
### 9.1 ConfigMaps
|
||||
|
||||
Erstellen aus einer Datei:
|
||||
|
||||
```bash
|
||||
kubectl create configmap app-config --from-file=config.properties
|
||||
```
|
||||
|
||||
Oder aus Literalwert:
|
||||
|
||||
```bash
|
||||
kubectl create configmap app-config --from-literal=KEY=VALUE
|
||||
```
|
||||
|
||||
ConfigMaps anzeigen:
|
||||
|
||||
```bash
|
||||
kubectl get configmaps
|
||||
kubectl describe configmap app-config
|
||||
```
|
||||
|
||||
### 9.2 Secrets
|
||||
|
||||
```bash
|
||||
kubectl create secret generic db-secret \
|
||||
--from-literal=username=user \
|
||||
--from-literal=password=geheim
|
||||
```
|
||||
|
||||
Achtung: Standard-`kubectl get secret -o yaml` zeigt Base64-codierte, aber nicht verschlüsselte Daten.
|
||||
|
||||
---
|
||||
|
||||
## 10. Weitere nützliche Features
|
||||
|
||||
### 10.1 Autocomplete
|
||||
|
||||
Bash-Beispiel:
|
||||
|
||||
```bash
|
||||
source <(kubectl completion bash)
|
||||
echo 'source <(kubectl completion bash)' >> ~/.bashrc
|
||||
```
|
||||
|
||||
### 10.2 Kustomize (ohne extra Tool, ab neueren kubectl-Versionen)
|
||||
|
||||
```bash
|
||||
kubectl apply -k ./overlays/production
|
||||
```
|
||||
|
||||
Struktur z.B.:
|
||||
|
||||
```text
|
||||
base/
|
||||
deployment.yaml
|
||||
kustomization.yaml
|
||||
overlays/production/
|
||||
kustomization.yaml
|
||||
patches.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Typische Praxis-Workflows
|
||||
|
||||
### 11.1 Neue Version deployen
|
||||
|
||||
```bash
|
||||
git pull
|
||||
kubectl apply -f k8s/
|
||||
kubectl rollout status deployment mein-service
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
### 11.2 Fehlerhafte Anwendung debuggen
|
||||
|
||||
```bash
|
||||
kubectl get pods
|
||||
kubectl describe pod fehler-pod
|
||||
kubectl logs fehler-pod
|
||||
kubectl exec -it fehler-pod -- sh
|
||||
kubectl get events --sort-by=.metadata.creationTimestamp
|
||||
```
|
||||
|
||||
### 11.3 Schnell in einen anderen Namespace/Cluster wechseln
|
||||
|
||||
```bash
|
||||
kubectl config use-context staging-cluster
|
||||
kubectl config set-context --current --namespace=team-a
|
||||
kubectl get pods
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Wenn du möchtest, kann ich dir für deinen konkreten Use Case (z.B. “Web-App deployen”, “CI/CD-Pipeline”, “lokales Minikube-Cluster”) eine konkrete Schritt-für-Schritt-Anleitung mit fertigen `kubectl`-Befehlen erstellen.
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
rsync -aP /c/Users/d92448/OneDrive\ -\ d-fine\ GmbH/Notizbücher/mathias_dfine_vault/mathias_dfine_vault/Know-How/ ~/dotfiles/notes/nextcloud/Notes/IT-Know-How/
|
||||
Reference in New Issue
Block a user