add synced notes on IT know how

This commit is contained in:
Mathias Schneider
2026-03-17 19:01:50 +01:00
parent b8797d5ca8
commit fc3aef4da8
31 changed files with 13162 additions and 1 deletions
-1
View File
@@ -1 +0,0 @@
/c/Users/d92448/OneDrive - d-fine GmbH/Notizbücher/mathias_dfine_vault/mathias_dfine_vault/Know-How/
+214
View File
@@ -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 1020
```
## 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
View File
@@ -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 **12 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.).
@@ -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
View File
@@ -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
View File
@@ -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:0000: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
View File
@@ -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]]/Lets 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
View File
@@ -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).
File diff suppressed because it is too large Load Diff
+368
View File
@@ -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
View File
@@ -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
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+540
View File
@@ -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
View File
@@ -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
View File
@@ -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 PythonApplikation (z.B. [[FastAPI]], Starlette, Django mit ASGI)
Uvicorn basiert intern auf sehr schnellen CBibliotheken:
- **uvloop** (schneller Event Loop, Ersatz für `asyncio`Loop)
- **httptools** (schnelles HTTPParsing)
Du verwendest uvicorn typischerweise, um eine ASGIApp „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 WebSocketSupport
- 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/ResponseLogik
- **uvicorn**:
- kümmert sich um das Annehmen von Verbindungen, HTTPParsing, EventLoopHandling.
- ruft deine Applikation nur gemäß dem ASGIProtokoll 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 syncApps 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 WorkerSpawner
- uvicorn = eigentlicher ASGIServer pro Worker
### 3.2 uvicorn vs. Hypercorn / Daphne
- **hypercorn**:
- anderer ASGIServer (unterstützt z.B. HTTP/2, verschiedene Event Loops)
- **daphne**:
- ASGIServer aus dem DjangoChannels‑Ökosystem
Alle drei (uvicorn, hypercorn, daphne) machen im Kern das Gleiche:
**ASGIApps 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 WebBackends 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 WebSocketEndpoint nicht möglich.
### 4.3 Produktionstauglicher Server gegenüber Entwicklungsservern
- Stabilität bei hoher Last
- Steuerung von:
- Anzahl WorkerProzesse
- 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 ASGIApp 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 CLIOptionen:
- `--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 CPUAufgaben
- synchrones Warten auf externe APIs (z.B. `requests.get(...)`)
- schwere DatenbankQueries, 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 syncDBClient)
- CPUlastig: in ThreadPool oder ProcessPool 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. DBConnections 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 LinuxServern oder in DockerContainern.
---
## 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**
- BlockingCode prüfen (CPU, I/O)
- Datenbankzugriff sauber konfigurieren (Pools, asyncClient)
- Logging und ErrorHandling 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 **ASGIWebserver** für Python, optimiert für **asynchrone** WebApps.
- Er ist **kein WebFramework**, sondern die Laufzeitumgebung für Frameworks wie **[[FastAPI]]**, **Starlette** oder moderne **Django**Konfigurationen.
- Es löst die Probleme klassischer WSGIServer in Bezug auf **Async**, **WebSockets** und **Performance**.
- Typische Herausforderungen liegen im Bereich:
- korrektes AsyncDesign
- Umgang mit mehreren Workern und gemeinsamem Zustand
- saubere Integration von Datenbanken, Logging, Deployment
- Für dich als PythonEntwickler ist uvicorn im Alltag vor allem:
**das Kommando, mit dem du deine moderne WebAPI 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 Layer7-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 APISystemen.
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. 515 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.
@@ -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-SourceSystem 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 (OnPrem) 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, CRIO, 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 @@
„Layer7Logik“ 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
**Layer4Routing**:
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.
**Layer7Logik**:
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 „Layer7Logik“ 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 PythonKontext?
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 PythonApp) an einen **Anwendungsserver** weiterleitet (z.B. Gunicorn, uWSGI, [[uvicorn]])
Im PythonKontext wird nginx fast immer als **„Vorderseite“ (Frontend) vor einer PythonWebanwendung** 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. Lets 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 Lets 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. 560 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.