diff --git a/ktouch/.local/share/ktouch/profiles.db b/ktouch/.local/share/ktouch/profiles.db index 5d0d079..bd32270 100644 Binary files a/ktouch/.local/share/ktouch/profiles.db and b/ktouch/.local/share/ktouch/profiles.db differ diff --git a/notes/nextcloud/Notes/IT-Know-How/Vim Cheatsheet.md b/notes/nextcloud/Notes/IT-Know-How/Vim Cheatsheet.md deleted file mode 100755 index b26fb32..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/Vim Cheatsheet.md +++ /dev/null @@ -1,214 +0,0 @@ -## 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 - - Zurück in Normalmodus - -v Visueller Modus (Zeichenweise) -V Visueller Modus (Zeilenweise) - 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 Zeichen ersetzen - -dd Zeile löschen (cut) -D Bis zum Zeilenende löschen -cc Zeile ändern (delete + insert) -C Bis zum Zeilenende ändern - -yy Zeile kopieren -Y Alias für yy -p Einfügen NACH Cursor/Zeile -P Einfügen VOR Cursor/Zeile - -u Undo -Ctrl-r Redo - -J Nächste Zeile an aktuelle anhängen ->> Zeile einrücken (indent) -<< Zeile ausrücken (unindent) -``` -## Mit Counts & Bewegungen -Viele Befehle funktionieren mit einer Anzahl (count) und einer Bewegung: -```text -3w 3 Wörter vor -5j 5 Zeilen runter - -d3w 3 Wörter löschen -c$ Bis Zeilenende ändern -y0 Bis Zeilenanfang kopieren -``` -## Textobjekte (im Normal- / Visuellen Modus) -```text -aw A word (inkl. Leerzeichen) -iw Inner word -as A sentence -is Inner sentence -ap A paragraph -ip Inner paragraph - -a" / i" Text in Anführungszeichen -a' / i' Text in einfachen Quotes -a) / i) Text in Klammern (usw. für {}, [], <>) - -Beispiele: -ci" Inhalt von "..." ändern -da( Klammerausdruck inklusive Klammern löschen -viw Wort unter Cursor markieren -``` -## Suchen & Ersetzen -```text -/word Vorwärts nach „word“ suchen -?word Rückwärts suchen -n Nächster Treffer -N Vorheriger Treffer - -* Wort unter Cursor vorwärts suchen -# Wort unter Cursor rückwärts suchen - -:%s/alt/neu/g In gesamter Datei ersetzen -:%s/alt/neu/gc Mit Bestätigung -:.,$s/alt/neu/g Von aktueller Zeile bis Ende -:10,20s/alt/neu/g In Zeilen 10–20 -``` -## Dateien, Buffers, Splits & Tabs -```text -:e datei Datei öffnen -:w Speichern -:w name Unter anderem Namen speichern -:q Beenden -:q! Beenden ohne Speichern -:wq / :x Speichern und beenden -:qa Alle Fenster schließen -:qa! Alle schließen ohne Speichern - -" Buffer (Dateien im Speicher) -:ls Buffer-Liste -:buffer n Buffer n öffnen -:bnext / :bn Nächster Buffer -:bprev / :bp Vorheriger Buffer -:bdelete Buffer schließen - -" Splits -:split datei Horizontaler Split -:vsplit datei Vertikaler Split -Ctrl-w s Split horizontal -Ctrl-w v Split vertikal -Ctrl-w w Zum nächsten Split -Ctrl-w h/j/k/l Split wechseln -Ctrl-w q Split schließen - -" Tabs -:tabnew Neuer Tab -:tabclose Tab schließen -:tabnext / :tn Nächster Tab -:tabprev / :tp Vorheriger Tab -``` -## Visual Mode Aktionen -```text -v / V / Ctrl-v Auswahl starten -y Auswahl kopieren -d Auswahl löschen -c Auswahl ändern - -> Einrücken -< Ausrücken -= Auto-Indent (z.B. für Code) - -: Befehl auf Auswahl anwenden - (Bereich wird automatisch eingetragen) -``` -## Makros & Wiederholung -```text -. Letzte Änderung wiederholen - -q 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 -``` diff --git a/notes/nextcloud/Notes/IT-Know-How/datenbanken/Identity Columns.md b/notes/nextcloud/Notes/IT-Know-How/datenbanken/Identity Columns.md deleted file mode 100755 index bcffa01..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/datenbanken/Identity Columns.md +++ /dev/null @@ -1,497 +0,0 @@ -#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 \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/datenbanken/ORM.md b/notes/nextcloud/Notes/IT-Know-How/datenbanken/ORM.md deleted file mode 100755 index c5c7730..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/datenbanken/ORM.md +++ /dev/null @@ -1,475 +0,0 @@ -## 1. Grundidee: Was ist ein ORM? - -ORM steht für **Object-Relational Mapping**. -Im Kontext von Python und Datenbankmodellen bedeutet das: - -> Ein ORM ist eine Bibliothek, die es dir erlaubt, mit einer relationalen Datenbank (z. B. PostgreSQL, MySQL, SQLite) zu arbeiten, indem du **Python-Klassen und -Objekte** verwendest statt SQL-Strings. - -Anstatt also SQL wie: - -```sql -SELECT * FROM konto WHERE id = 1; -``` - -zu schreiben, machst du in Python z. B.: - -```python -konto = session.get(Konto, 1) -``` - -Das ORM übersetzt deine Python-Anweisung im Hintergrund in SQL, führt sie aus und gibt dir Python-Objekte zurück. - ---- - -## 2. Wichtige Begriffe und Bausteine (intuitiv erklärt) - -Nehmen wir ein vereinfachtes Bank-Beispiel: - -- **Kunde** (Customer) -- **Konto** (Account) -- **Transaktion** (Transaction) - -In einer relationalen Datenbank wären das Tabellen: - -- `kunde` -- `konto` -- `transaktion` - -Mit einem ORM legst du dafür **Modelle** als Python-Klassen an: - -```python -class Kunde: - ... -class Konto: - ... -class Transaktion: - ... -``` - -Die wichtigsten Bausteine: - -- **Modell / Entity**: - Eine Klasse, die eine Tabelle repräsentiert (z. B. `Konto` → Tabelle `konto`). -- **Felder / Spalten**: - Attribute der Klasse (z. B. `saldo`, `kunde_id`). -- **Beziehungen**: - - „Ein Kunde hat viele Konten“ (One-to-Many) - - „Ein Konto gehört zu genau einem Kunden“ (Many-to-One) -- **Session / EntityManager**: - Ein Objekt, über das du mit der Datenbank sprichst (z. B. Datensätze abfragen, speichern, löschen). - ---- - -## 3. Abgrenzung zu verwandten Begriffen - -### 3.1 ORM vs. direkte SQL-Nutzung - -**Direkte SQL-Nutzung:** - -- Du schreibst selbst SQL-Statements. -- Du verwendest z. B. `psycopg2` (PostgreSQL) oder `sqlite3` (Standard in Python). -- Du bekommst Resultate als Tupel / Dictionaries zurück und baust selbst deine Objekte. - -**Mit ORM:** - -- Du arbeitest mit Python-Klassen und -Objekten. -- Das ORM generiert und führt SQL für dich aus. -- Du bekommst direkt Instanzen deiner Klassen. - -```python -# Direkte SQL-Nutzung -cursor.execute("SELECT id, name FROM kunde WHERE id = %s", (1,)) -row = cursor.fetchone() -kunde = {"id": row[0], "name": row[1]} - -# ORM -kunde = session.get(Kunde, 1) -print(kunde.name) -``` - -ORM nimmt dir also den „manuellen“ Teil des Mappings ab. - ---- - -### 3.2 ORM vs. Datenbankmodellierung (ER-Modell) - -- **Datenbankmodellierung** (ER-Diagramme, Normalisierung usw.) ist der Schritt, in dem du **konzipierst**, wie deine Daten strukturiert sind. -- **ORM** ist ein **Werkzeug**, um mit dieser Struktur in Python zu arbeiten. - -Du kannst ein gutes Datenbankmodell haben – **mit oder ohne** ORM. -ORM ersetzt nicht das Nachdenken über ein sinnvolles Datenmodell. - ---- - -### 3.3 ORM vs. Migrations-Tools - -- **Migrations-Tools** (z. B. Alembic für SQLAlchemy, Django-Migrations) verwalten die **Versionierung und Änderungen** am Schema (z. B. neue Spalte, geänderte Spalte). -- Ein **ORM** arbeitet primär zur **Laufzeit** mit Daten; viele ORMs bringen allerdings Tools mit, um aus den Modellen Migrationen zu erzeugen. - ---- - -### 3.4 ORM vs. Query-Builder - -- **Query-Builder**: Bibliothek, die das Schreiben von SQL erleichtert, aber nicht unbedingt Objekte modelliert (z. B. SQLAlchemy Core). -- **ORM**: baut auf einem Query-Builder auf und bringt zusätzlich: - - Klassen→Tabellen-Mapping - - Objekte→Zeilen-Mapping - - Beziehungen als Attribute - ---- - -### 3.5 ORM vs. [[Pydantic]] / Dataclasses - -- **[[Pydantic]] / `dataclasses`**: Modellieren **In-Memory-Daten** (z. B. Input aus einer API), inkl. Validation und Typen. -- **ORM**: Modelliert **persistente Daten** in einer relationalen Datenbank. - -Man kombiniert das oft: - -- [[Pydantic]]-Modelle für API-Ein-/Ausgaben -- ORM-Modelle für Speicherung in der Datenbank - ---- - -## 4. Welche Probleme löst ein ORM? - -### 4.1 „Impedance Mismatch“: Objekte vs. Tabellen - -Python arbeitet mit **Objekten**: - -```python -kunde.name -konto.saldo -konto.kunde.name -``` - -Datenbanken arbeiten mit **Tabellen**, **Zeilen** und **Fremdschlüsseln**. -ORMs „übersetzen“ zwischen diesen Welten. - -Beispiel: - -- `konto.kunde` ist in Python einfach ein Attribut. -- Intern bedeutet das: Joins über `konto.kunde_id = kunde.id`. - ---- - -### 4.2 Weniger Boilerplate, mehr Fokus auf Fachlogik - -Ohne ORM schreibst du viel repetiven Code: - -- SQL-Strings -- Parameter-Bindung -- Zeilen in Python-Objekte umwandeln - -Mit ORM schreibst du einmal deine Modelle und konzentrierst dich dann auf: - -- „Neue Transaktion buchen“ -- „Saldo prüfen“ -- „Kontoauszug generieren“ - -Anstatt: „Wie formuliere ich nochmal den SQL-Join...“. - ---- - -### 4.3 Typisierung, Autocomplete, Konsistenz - -Durch Python-Klassen hast du: - -- **Typhinweise** (z. B. `saldo: float`) -- Unterstützung durch IDE (Autocomplete, Refactoring) -- Klar definierte Beziehungen (z. B. `konto.kunde`) - ---- - -### 4.4 Datenbank-Agnostik - -Viele ORMs unterstützen mehrere Datenbanken: - -- Du kannst z. B. in Tests SQLite verwenden, -- in Produktion PostgreSQL, -- ohne deinen ganzen Code umzuschreiben (meist nur Konfiguration). - ---- - -### 4.5 Testbarkeit - -Du kannst: - -- einfacher Unit-Tests schreiben, indem du In-Memory-SQLite nutzt, -- oder sogar nur mit „Fake-Repositories“ arbeitest, die sich wie das ORM verhalten. - ---- - -## 5. Welche Herausforderungen bringen ORMs mit sich? - -### 5.1 Performance-Fallen („N+1-Problem“, zu viele Queries) - -Beispiel: Du lädst 100 Konten und für jedes Konto den zugehörigen Kunden: - -Naiv: - -```python -konten = session.query(Konto).all() -for konto in konten: - print(konto.kunde.name) -``` - -Kann bedeuten: - -- 1 Query für alle Konten -- + 100 Queries für jeden einzelnen Kunden - -→ Insgesamt 101 Queries (= N+1-Problem). - -Mit ORM musst du lernen, wie man: - -- **Eager Loading** / `join` / `selectinload` etc. nutzt, -- um nur **1–2 Queries** zu erzeugen. - ---- - -### 5.2 Man darf SQL nicht völlig „vergessen“ - -ORM nimmt viel Arbeit ab, aber: - -- du solltest **verstehen**, was für SQL generiert wird, -- und Grundbegriffe wie `JOIN`, `WHERE`, `GROUP BY`, Indexe kennen. - -Ohne grundlegende SQL-Kenntnisse tappst du schnell in Performance-Probleme. - ---- - -### 5.3 Komplexe Abfragen - -Für einfache Abfragen ist ORM sehr angenehm. -Bei sehr komplexen Auswertungen (z. B. Bank-Reporting mit vielen Aggregationen, Window-Funktionen) kann: - -- die ORM-Syntax unübersichtlich werden, -- rohe SQL-Statements manchmal klarer und effizienter sein. - -Viele ORMs erlauben gemischt: - -- 90 % ORM, -- 10 % direktes SQL für Spezialfälle. - ---- - -### 5.4 Migrationen und Schema-Änderungen - -- Du musst im Blick behalten: **Modelle in Python** und **Schema in Datenbank** dürfen nicht auseinanderlaufen. -- Migrations-Tools sind notwendig, aber auch ein eigener Lernbereich. - ---- - -### 5.5 „Lock-In“ und Komplexität - -- Große ORMs (z. B. SQLAlchemy ORM, Django ORM) sind mächtig, aber haben Lernkurve. -- Wenn du einmal dein ganzes Projekt auf ein bestimmtes ORM gebaut hast, ist ein Wechsel auf ein anderes ORM oder „plain SQL“ aufwendig. - ---- - -## 6. Praxisnahe Beispiele mit Python (SQLAlchemy ORM) - -### 6.1 Setup: einfache Bank-Domain mit SQLAlchemy - -Installation: - -```bash -pip install sqlalchemy -``` - -Ein minimaler Aufbau mit `Kunde` und `Konto`: - -```python -from sqlalchemy import ( - create_engine, Column, Integer, String, Float, ForeignKey -) -from sqlalchemy.orm import ( - declarative_base, relationship, Session -) - -# Basis-Klasse für alle ORM-Modelle -Base = declarative_base() - -class Kunde(Base): - __tablename__ = "kunde" - - id = Column(Integer, primary_key=True) - name = Column(String, nullable=False) - - # Beziehung: Ein Kunde hat viele Konten - konten = relationship("Konto", back_populates="kunde") - - def __repr__(self): - return f"" - -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"" - -# 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.). - diff --git a/notes/nextcloud/Notes/IT-Know-How/datenbanken/PostgreSQL/PostgreSQL Umstieg von Oracle SQL.md b/notes/nextcloud/Notes/IT-Know-How/datenbanken/PostgreSQL/PostgreSQL Umstieg von Oracle SQL.md deleted file mode 100755 index 09eff2d..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/datenbanken/PostgreSQL/PostgreSQL Umstieg von Oracle SQL.md +++ /dev/null @@ -1,346 +0,0 @@ -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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/glossar/ACID-Transaktionen.md b/notes/nextcloud/Notes/IT-Know-How/glossar/ACID-Transaktionen.md deleted file mode 100755 index a5a6d6c..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/glossar/ACID-Transaktionen.md +++ /dev/null @@ -1,384 +0,0 @@ -#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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/glossar/CTEs.md b/notes/nextcloud/Notes/IT-Know-How/glossar/CTEs.md deleted file mode 100755 index 9f61892..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/glossar/CTEs.md +++ /dev/null @@ -1,549 +0,0 @@ -#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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/glossar/MVCC.md b/notes/nextcloud/Notes/IT-Know-How/glossar/MVCC.md deleted file mode 100755 index cb4993f..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/glossar/MVCC.md +++ /dev/null @@ -1,384 +0,0 @@ -#datenbank -➡️[[#Zusammenfassung]] - ---- -MVCC („==Multi-Version Concurrency Control==“) ist ein Verfahren, mit dem Datenbanken gleichzeitige Zugriffe (Concurrency) von vielen Benutzern/Programmen steuern, ohne dass sie sich ständig gegenseitig blockieren. - -Ich gehe Schritt für Schritt vor: - -1. Ausgangspunkt: Was ist das Problem überhaupt? -2. Grunddefinition von MVCC (für Einsteiger) -3. Wie funktioniert das intern grob? -4. Abgrenzung zu verwandten Konzepten -5. Welche Probleme löst MVCC konkret? -6. Welche Herausforderungen/Nachteile bringt MVCC mit sich? -7. Praxisnahe Beispiele - ---- - -## 1. Ausgangspunkt: Wozu brauche ich so etwas? - -In einer typischen Anwendung greifen viele Nutzer gleichzeitig auf dieselbe Datenbank zu: - -- Nutzer A liest Daten (z.B. Produktliste im Shop) -- Nutzer B aktualisiert Daten (z.B. ändert Preise) -- Nutzer C führt einen großen Report aus (z.B. tägliche Umsatzauswertung) - -Ziel: -Alle sollen gleichzeitig arbeiten können, **ohne Fehler** wie: - -- „Dirty Reads“ (jemand liest halbfertige Änderungen) -- Inkonsistente Sicht auf die Daten -- Datenverlust, weil gleichzeitige Änderungen sich überschreiben -- Dauerhafte Sperren und Deadlocks, die das System verlangsamen oder blockieren - -Dazu gibt es das Konzept der **Transaktionen** (ACID): - -- **A**tomicity: Alles oder nichts. -- **C**onsistency: Regeln der Datenbank bleiben erfüllt. -- **I**solation: Transaktionen beeinflussen sich möglichst wenig. -- **D**urability: Einmal bestätigte Daten gehen nicht verloren. - -Die Isolation ist das Kernfeld, in dem MVCC ins Spiel kommt: -Wie sorgt man dafür, dass viele Transaktionen **gleichzeitig** laufen können, ohne sich ständig zu blockieren? - ---- - -## 2. Grunddefinition von MVCC (für Einsteiger) - -**MVCC (Multi-Version Concurrency Control)** bedeutet: - -- **Jede Zeile (Row) in einer Tabelle existiert in mehreren Versionen.** -- Jede Transaktion sieht die Daten „so, wie sie zu einem bestimmten Zeitpunkt waren“ – sie arbeitet mit einem **Snapshot** (Momentaufnahme). -- Wenn jemand etwas **ändert**, wird **nicht** die existierende Zeile überschrieben, sondern eine **neue Version** der Zeile angelegt. -- Für jede Transaktion gibt es Regeln, **welche Version** einer Zeile sie sehen darf. - -Anschaulich: - -- Stell dir eine Tabelle als Tabelle in Excel vor. -- MVCC macht von einigen Zeilen „Kopien“, wenn sie geändert werden. -- Du (deine Transaktion) siehst eine konsistente Version des Tabellenblatts zu dem Zeitpunkt, als du angefangen hast – auch wenn andere währenddessen schon neue Versionen erzeugt haben. - ---- - -## 3. Wie funktioniert MVCC grob intern? - -Vereinfachtes Modell (z.B. ähnlich wie in PostgreSQL/InnoDB): - -### 3.1. Versionen von Zeilen - -Statt einer Zeile gibt es: - -- Version 1: erstellt von Transaktion T1 -- Version 2: später erstellt von Transaktion T2 (z.B. nach einem UPDATE) -- Version 3: später erstellt von T3, usw. - -Jede Version hat Metadaten, z.B.: - -- „ab Transaktion X gültig“ -- „bis Transaktion Y gültig“ - (oder „gelöscht ab Transaktion Y“) - -### 3.2. Was sieht eine Transaktion? - -Wenn eine neue Transaktion T liest: - -- Sie merkt sich einen „Zeitpunkt“ (z.B. eine Transaktions-ID oder Timestamp). -- Beim Lesen prüft sie pro Zeile: - - - Wurde diese Version **vor** meinem Start erzeugt und war zu dem Zeitpunkt schon committed? - - Wurde sie danach erzeugt? Dann darf ich sie nicht sehen. - - Ist sie bereits gelöscht oder von einer laufenden Transaktion verändert, die noch nicht committed ist? Dann gilt sie für mich ggf. als nicht existent. - -Das Ergebnis: -Du bekommst einen **konsistenten Snapshot** der Daten – so, als ob alle Änderungen nach deinem Start noch nicht existieren. - -### 3.3. Schreiben mit MVCC - -Beim **UPDATE** einer Zeile: - -1. Alte Version bleibt im Speicher (zunächst noch sichtbar für alte Transaktionen). -2. Neue Version wird erzeugt, mit „gültig ab Transaktion T“. -3. Für neu startende Transaktionen ist **nach Commit** nur noch die neue Version relevant. - -Beim **DELETE**: - -- Die Zeile bekommt sozusagen ein „Todes-Zeitstempel“ (gültig bis T). -- Alte Transaktionen sehen sie noch, neue nicht mehr. - -### 3.4. Aufräumen („Vacuum“, Garbage Collection) - -Alte Versionen, die **keine** Transaktion mehr sehen kann, sind Müll: - -- Es gibt Prozess(e), die diese alten Versionen von Zeit zu Zeit **physisch entfernen**. -- Beispiel-Begriff: in PostgreSQL „VACUUM“. - ---- - -## 4. Abgrenzung zu verwandten Konzepten - -### 4.1. MVCC vs. „klassische“ Sperren (Locking) - -Früher (oder in einfachen Systemen): - -- Jede Transaktion, die liest oder schreibt, setzt **Sperren** (Locks) auf Zeilen oder ganze Tabellen. -- Wenn du eine Zeile liest, kann ein strenges System diese Zeile sperren, sodass andere sie nicht ändern dürfen, bis du fertig bist. -- Das kann zu Blockaden und Deadlocks führen. - -**MVCC-Ansatz:** - -- Leser blockieren Schreiber **nicht** (oder nur minimal). -- Schreiber blockieren Leser **nicht** (solange es nur um das Lesen bereits existierender, „alter“ Versionen geht). -- Es gibt zwar weiterhin Sperren, aber sie sind oft nur für Konfliktfälle relevant (z.B. zwei Transaktionen, die **dieselbe** Zeile gleichzeitig ändern wollen). - -MVCC ersetzt also nicht jede Form von Locking, sondern **reduziert** deren Notwendigkeit für Lesezugriffe stark. - -### 4.2. MVCC vs. Snapshot Isolation - -- **MVCC** ist ein **Mechanismus**: mehrere Versionen von Zeilen, Snapshots, Sichtbarkeitsregeln. -- **Snapshot Isolation** ist eine **Isolationsebene** (ein bestimmter Garantiesatz für Transaktionen), die **auf MVCC aufbaut**. - -Viele Systeme verwenden MVCC, um Snapshot Isolation umzusetzen: - -- Jede Transaktion sieht den Datenbestand so, wie er beim Start (oder beim Start des Statements) war. -- Aber: Snapshot Isolation ist oft **nicht vollständig serialisierbar** (siehe Write Skew Problem weiter unten). - -Kurz: -MVCC = Wie die Datenbank Versionen verwaltet. -Snapshot Isolation = Welche Sicht und Garantien Transaktionen bekommen. - -### 4.3. MVCC vs. Optimistic Concurrency Control (OCC) - -- **Optimistic Concurrency Control** geht davon aus: Konflikte sind selten, man lässt Transaktionen frei operieren und prüft erst beim Commit, ob es Konflikte gab. -- Viele MVCC-Systeme verwenden **Elemente von OCC**: Sie lassen Transaktionen auf einem Snapshot arbeiten und prüfen erst beim Commit, ob sie z.B. eine Zeile verändert haben, die inzwischen jemand anders schon geändert hat. - -MVCC ist also kein direkter Gegensatz zu OCC, sondern eher eine Technik, mit der man OCC effizient implementieren kann. - ---- - -## 5. Welche Probleme werden durch MVCC gelöst? - -### 5.1. Leser blockieren Schreiber nicht (und umgekehrt) - -**Problem ohne MVCC:** -Großer Report läuft lange und sperrt viele Zeilen oder Tabellen. Schreibende Transaktionen können nicht mehr weiterarbeiten. - -**Mit MVCC:** - -- Der Report bekommt einen Snapshot. -- Während der Report läuft: - - Andere Transaktionen können neue Versionen schreiben. - - Der Report liest weiterhin die alten Versionen, die für ihn gültig sind. -- Ergebnis: lange lesende Transaktionen blockieren das System weit weniger. - -**Praxisbeispiel:** - -- Ein Online-Shop: - - Ein Reporting-Job zählt alle Bestellungen des letzten Monats. - - Gleichzeitig legen Kunden neue Bestellungen an. -- Mit MVCC: - - Reporting-Job sieht einen stabilen Stand („Stand 10:00 Uhr“). - - Kunden können weiter bestellen; diese Bestellungen erscheinen evtl. nicht im aktuellen Report, aber im nächsten. - -### 5.2. Konsistente Lese-Sichten - -Lesende Transaktionen sehen eine **konsistente Momentaufnahme**, nicht einen Mix aus „halb alt, halb neu“. - -Beispiel: - -- Du willst sicherstellen, dass Summe aller Kontosalden = 0 (Bilanz). -- Ohne MVCC könntest du beim Lesen ein Konto schon nach der neuen Buchung sehen, ein anderes aber noch vor der Buchung – Ergebnis: inkonsistente Summe. -- Mit MVCC liest du alles so, wie es zu deinem Startzeitpunkt war. - -### 5.3. Hohe Skalierbarkeit für viele Leser - -MVCC ist besonders stark in Systemen mit: - -- hohem Leseanteil (Reports, Dashboards, APIs) -- vielen parallelen Nutzern - -Weil Leser nicht permanent Schreiblocks blockieren, kann das System mehr parallele Anfragen verarbeiten. - -### 5.4. Reduzierte Deadlocks - -Deadlocks entstehen oft, wenn zwei Transaktionen sich gegenseitig Sperren wegnehmen wollen. -Da MVCC Leser weitgehend von Locks entkoppelt, reduziert sich die Deadlock-Wahrscheinlichkeit, insbesondere bei reinen Lesezugriffen. - ---- - -## 6. Welche Herausforderungen und Nachteile hat MVCC? - -### 6.1. Speicher-Overhead durch viele Versionen - -Da alte Versionen zunächst liegen bleiben, kann es passieren: - -- Tabellen werden „aufgebläht“ (Bloat). -- Disk-Verbrauch steigt, Indexe werden größer, Performance sinkt. - -Darum braucht man: - -- Mechanismen zur Bereinigung (VACUUM, Garbage Collection) -- Tuning (z.B. wie aggressiv wird gereinigt?) - -**Praxisproblem:** -Ein System mit dauernd vielen Updates/Deletes, aber schlecht eingestelltem Vacuum, kann plötzlich deutlich langsamer werden, weil die Tabellen intern riesig sind. - -### 6.2. Lang laufende Transaktionen halten alte Versionen fest - -Wenn eine Transaktion sehr lange offen bleibt (z.B. ein Report, der 2 Stunden läuft): - -- Alle Versionen, die **für diese Transaktion sichtbar sein könnten**, müssen erhalten bleiben. -- Die Datenbank kann diese Versionen nicht löschen, weil sie noch gebraucht werden. -- Folge: Bloat, mehr Speicherverbrauch, mehr I/O, langsamere Zugriffe. - -Praxisnahe Konsequenz: - -- Man versucht in produktiven Systemen: - - Lange Transaktionen zu vermeiden oder - - Sie auf Replikas/Read-Only-Kopien auszulagern. - -### 6.3. Anomalien unter Snapshot Isolation (Write Skew) - -Snapshot Isolation (häufig via MVCC umgesetzt) ist oft **nicht vollständig serialisierbar**. - -Beispiel (vereinfacht): - -- Zwei Ärzte müssen gleichzeitig im Krankenhaus Dienst haben. -- Regel: Es dürfen nie **beide** Ärzte gleichzeitig „Off-Duty“ sein. -- Tabelle `dienstplan` mit Einträgen pro Arzt. - -Transaktion T1: - -```sql -BEGIN; -SELECT COUNT(*) FROM dienstplan WHERE on_duty = TRUE; --- Ergebnis: 2 (Arzt A und B sind im Dienst) - --- T1 setzt Arzt A auf off-duty -UPDATE dienstplan SET on_duty = FALSE WHERE arzt = 'A'; -COMMIT; -``` - -Transaktion T2 (parallel, mit eigenem Snapshot): - -```sql -BEGIN; -SELECT COUNT(*) FROM dienstplan WHERE on_duty = TRUE; --- Ergebnis: 2 (sieht noch alten Stand, beide on duty) - --- T2 setzt Arzt B auf off-duty -UPDATE dienstplan SET on_duty = FALSE WHERE arzt = 'B'; -COMMIT; -``` - -Beide Transaktionen sehen **denselben alten Stand** („2 Ärzte im Dienst“). -Beide halten es für zulässig, je einen Arzt auf off-duty zu setzen. -Ergebnis nach beiden Commits: **0 Ärzte im Dienst**, Regel verletzt. - -Das ist ein typischer **Write Skew** – möglich unter Snapshot Isolation, obwohl MVCC sauber arbeitet. - -Lösung: - -- Strengere Isolationsebene: **SERIALIZABLE** (die DB verhindert durch zusätzliche Checks solche Fälle), - oder -- Explizite Sperren / zusätzliche Constraints durch den Entwickler. - -### 6.4. Komplexere Implementierung und Tuning - -MVCC ist intern relativ komplex: - -- Verwaltung von Versionen -- Sichtbarkeitslogik -- Garbage Collection -- Umgang mit Hot-Spot-Tabellen (sehr häufig aktualisierte Zeilen) - -Für Administratoren/Entwickler bedeutet das: - -- Man muss verstehen, wie die eigene DB MVCC implementiert (PostgreSQL, MySQL InnoDB, Oracle, SQL Server etc. machen es leicht unterschiedlich). -- Man muss Parameter fürs Aufräumen und für Isolationsebenen sinnvoll setzen. - ---- - -## 7. Praxisnahe Beispiele - -### Beispiel 1: Webshop – Produktpreise ändern - -Situation: - -- Tabelle `produkte` mit Spalte `preis`. -- Viele Nutzer sehen gleichzeitig das Produkt im Shop. -- Ein Admin ändert den Preis. - -Ohne MVCC (vereinfachte, pessimistische Sperrung): - -- Admin setzt Lock auf Zeile/Produkt. -- Solange Admin noch nicht committed hat: - - Kunden könnten blockiert werden oder - - sie sehen unklare Zwischenzustände. - -Mit MVCC: - -- Admin-Transaktion erzeugt eine **neue Version** der Produktzeile mit neuem Preis. -- Kunden, die vor dem Commit lesen: - - sehen die **alte Version** (alten Preis). -- Kunden, die nach dem Commit lesen: - - sehen die **neue Version** (neuen Preis). -- Es gibt keine Lese-Blockade während der Preisänderung. - -### Beispiel 2: Reporting vs. Online-Transaktionen - -- Ein Finance-Report soll **Stand Tagesende** alle Buchungen auswerten. -- Parallel buchen die Nutzer weiter. - -Mit MVCC: - -- Report bekommt einen Snapshot (z.B. Stand 23:59). -- Er arbeitet vielleicht 30 Minuten oder länger. -- Währenddessen können Buchungen von 00:00–00:30 weiter eingehen. -- Report sieht einen stabilen Zustand; neue Buchungen tauchen erst im nächsten Report auf. - -Das ist ideal für: - -- Data Warehouse Light -- tägliche Berichte -- Audits (reproduzierbare Sicht) - -### Beispiel 3: Problemfall lange Transaktion - -- Ein Entwickler startet in der Entwicklungsumgebung eine Transaktion: - -```sql -BEGIN; -SELECT * FROM grosse_tabelle; -- dauert lange --- Entwickler vergisst COMMIT oder ROLLBACK -``` - -- Diese Transaktion bleibt offen. -- Alle Versionen, die seit Beginn dieser Transaktion geändert wurden, müssen aufgehoben werden. -- Vacuum/Garbage Collector kann viele alte Zeilen nicht löschen. -- Nach Stunden/Tagen: - - Tabellen aufgebläht, - - Performance sinkt, - - Admin muss offene Sessions identifizieren und beenden. - -Lernpunkt: -Mit MVCC muss man auf **offene, vergessene Transaktionen** achten. - ---- - -## Zusammenfassung - -- **MVCC** bedeutet: Zeilen werden nicht einfach überschrieben, sondern es gibt mehrere Versionen derselben Zeile. -- Leser und Schreiber können gleichzeitig arbeiten: - - Leser sehen einen konsistenten Snapshot, - - Schreiber erzeugen neue Versionen, ohne Leser direkt zu blockieren. -- MVCC ist der Mechanismus, auf dem Isolationsebenen wie **Snapshot Isolation** aufbauen. -- Vorteile: - - Hohe Parallelität, - - Weniger Sperrkonflikte, - - Konsistente Sichten für Reports. -- Nachteile/Herausforderungen: - - Mehr Speicherbedarf durch alte Versionen, - - Notwendigkeit von Aufräumprozessen, - - Vorsicht bei langen Transaktionen, - - bestimmte Anomalien (z.B. Write Skew) bei Snapshot Isolation. diff --git a/notes/nextcloud/Notes/IT-Know-How/glossar/Materialized Views.md b/notes/nextcloud/Notes/IT-Know-How/glossar/Materialized Views.md deleted file mode 100755 index 8095313..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/glossar/Materialized Views.md +++ /dev/null @@ -1,360 +0,0 @@ -#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. diff --git a/notes/nextcloud/Notes/IT-Know-How/python/Asynchron in Python.md b/notes/nextcloud/Notes/IT-Know-How/python/Asynchron in Python.md deleted file mode 100755 index 8d61787..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/Asynchron in Python.md +++ /dev/null @@ -1,411 +0,0 @@ -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. - diff --git a/notes/nextcloud/Notes/IT-Know-How/python/FastAPI.md b/notes/nextcloud/Notes/IT-Know-How/python/FastAPI.md deleted file mode 100755 index df89f13..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/FastAPI.md +++ /dev/null @@ -1,499 +0,0 @@ -Im Folgenden bekommst du eine umfassende, aber einsteigerfreundliche Einführung in FastAPI. - ---- - -## 1. Grundidee: Was ist FastAPI? - -**FastAPI** ist ein modernes, schnelles Web-Framework für Python, mit dem du **Web-APIs** (Schnittstellen) bauen kannst. -Eine API ist eine „Schnittstelle“, über die andere Programme mit deinem Programm sprechen können – z. B. eine Web-App, ein Mobile-App-Backend oder interne Services in einem Unternehmen. - -Kernpunkte von FastAPI: - -- **Schwerpunkt:** Aufbau von **HTTP-APIs** (REST-APIs, JSON-basierte APIs). -- **Geschwindigkeit:** Sehr performant durch Nutzung von **asynchronem Python** (`async`/`await`), basierend auf **ASGI**. -- **Typisierung:** Starke Nutzung von **Python-Typannotationen** (z. B. `str`, `int`, eigene Klassen). - → Daraus entstehen automatisch: - - Validierung von Daten, - - automatische Dokumentation (Swagger / OpenAPI), - - bessere IDE-Unterstützung (Autovervollständigung, Fehlererkennung). -- **Auto-Dokumentation:** FastAPI generiert automatisch eine **interaktive API-Dokumentation** im Browser. - -Ein typisches „Hello World“ mit FastAPI sieht so aus: - -```python -from fastapi import FastAPI - -app = FastAPI() - -@app.get("/") -def read_root(): - return {"message": "Hello World"} -``` - -Starten kannst du das z. B. mit: - -```bash -uvicorn main:app --reload -``` - -Dann ist die API z. B. unter `http://127.0.0.1:8000` erreichbar. - ---- - -## 2. Abgrenzung: FastAPI vs. verwandte Begriffe und Frameworks - -### 2.1 FastAPI vs. „API“ / REST / HTTP allgemein - -- **HTTP**: Das zugrunde liegende Protokoll, über das Browser oder andere Dienste kommunizieren. -- **REST-API**: Eine Art, HTTP-APIs zu strukturieren (z. B. `GET /users`, `POST /orders`). -- **FastAPI**: Ein **Framework**, das dir hilft, solche HTTP/REST-APIs in Python zu bauen. - -FastAPI „spricht“ also HTTP, baut REST-APIs, ist aber selbst das **Werkzeug**, kein Protokoll. - ---- - -### 2.2 FastAPI vs. Flask - -**Flask** ist ein sehr bekanntes, minimalistisches Python-Webframework. - -**Ähnlichkeiten:** - -- Beide erlauben es, mit wenig Code HTTP-Endpunkte zu definieren. -- Beide sind relativ leichtgewichtig und flexibel. - -**Unterschiede:** - -- **Asynchronität**: - - Flask: traditionell synchron (WSGI), Async ist erst neuerdings und eingeschränkt verfügbar. - - FastAPI: von Anfang an für **async** gebaut (ASGI). -- **Typen & Validierung**: - - Flask: Kein eingebautes System für automatische Validierung – du machst das selbst oder mit Erweiterungen. - - FastAPI: Nutzt **[[Pydantic]]**-Modelle und Typannotationen → automatische Validierung. -- **Dokumentation**: - - Flask: Kein automatisches API-Dokumentations-UI. - - FastAPI: Automatisch generierte OpenAPI/Swagger-UI unter `/docs` und `/redoc`. - -Praxisbeispiel Vergleich: - -**Flask:** - -```python -from flask import Flask, request, jsonify - -app = Flask(__name__) - -@app.route("/items", methods=["POST"]) -def create_item(): - data = request.get_json() - name = data.get("name") - price = data.get("price") - if not isinstance(name, str) or not isinstance(price, (int, float)): - return jsonify({"error": "Invalid data"}), 400 - return jsonify({"name": name, "price": price}) -``` - -**FastAPI:** - -```python -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - -class Item(BaseModel): - name: str - price: float - -@app.post("/items") -def create_item(item: Item): - # item ist schon validiert - return item -``` - -FastAPI übernimmt hier die Validierung automatisch. - ---- - -### 2.3 FastAPI vs. Django (und Django REST Framework) - -**Django** ist ein „Full-Stack“-Framework: - -- liefert Templates, ORM (Datenbankzugriff), Admin-Interface, Auth-System, Formulare etc. -- ideal für klassische Webanwendungen mit HTML-Seiten. - -Für APIs nutzt man meist **Django REST Framework (DRF)** als Erweiterung. - -**FastAPI** dagegen ist: - -- stärker auf **APIs** fokussiert, -- nicht „alles aus einer Hand“, sondern: - - Web-Layer: `Starlette`, - - Datenvalidierung: `Pydantic`, - - Datenbank: du wählst selbst z. B. SQLAlchemy, Tortoise ORM etc. - -Faustregel: - -- Wenn du eine klassische Website mit HTML-Rendering brauchst → Django. -- Wenn du primär eine performant API bauen willst (z. B. für SPA, Microservices) → FastAPI ist sehr attraktiv. - ---- - -### 2.4 FastAPI vs. Node.js / Express - -**Node.js + Express** ist eine sehr verbreitete Lösung für APIs in JavaScript/TypeScript. - -- **Sprache:** Node → JavaScript/TypeScript, FastAPI → Python. -- **Typen:** TypeScript kann Typen bieten, FastAPI nutzt Python-Typen + [[Pydantic]]. -- **Ökosystem:** Node sehr stark im Web-/Frontend-nahen Bereich, Python stark bei Data Science, Machine Learning und Backend-Services. - -FastAPI ist besonders interessant, wenn du sowieso Python nutzt (z. B. wegen ML/AI) und dafür eine passende Web-API brauchst. - ---- - -## 3. Welche Probleme löst FastAPI? - -### 3.1 Saubere, valide Eingabedaten - -Problem ohne Framework-Unterstützung: - -- Du bekommst z. B. einen JSON-Body und musst: - - alle Felder prüfen (Typ, Pflichtfelder, Wertebereiche), - - Fehler verständlich zurückgeben, - - alles manuell machen. - -FastAPI + [[Pydantic]] lösen das: - -```python -from fastapi import FastAPI -from pydantic import BaseModel, Field - -app = FastAPI() - -class User(BaseModel): - name: str = Field(..., min_length=3) - age: int = Field(..., ge=0, le=120) # 0 <= age <= 120 - -@app.post("/users") -def create_user(user: User): - # Wenn name zu kurz oder age negativ ist, liefert FastAPI automatisch 422 mit Fehlerdetails - return {"message": "User created", "user": user} -``` - -Vorteil: - -- Weniger Fehleranfälligkeit. -- Konsistente Fehlerantworten. -- Gute Developer-Erfahrung. - ---- - -### 3.2 Automatische Dokumentation und Testbarkeit - -FastAPI erzeugt automatisch eine OpenAPI-Spezifikation und UI: - -- `http://localhost:8000/docs` → Swagger UI (interaktive Oberfläche, du kannst Requests direkt aus dem Browser abschicken). -- `http://localhost:8000/redoc` → ReDoc, alternative Dokumentationsansicht. - -Das hilft: - -- Dir selbst beim Testen. -- Frontend-Entwicklern oder anderen Teams, die deine API nutzen. -- Beim automatisierten Generieren von Client-SDKs (z. B. TypeScript-Client). - ---- - -### 3.3 Performance und asynchrones I/O - -Problem: - -- In „klassischen“ synchronen Webframeworks blockiert jeder Request, der z. B. auf eine externe API oder langsame DB wartet. -- Bei vielen gleichzeitigen Anfragen leiden Durchsatz und Antwortzeit. - -FastAPI setzt auf **ASGI** (Asynchronous Server Gateway Interface) und `async def`: - -```python -from fastapi import FastAPI -import httpx # asynchroner HTTP-Client - -app = FastAPI() - -@app.get("/external") -async def call_external_api(): - async with httpx.AsyncClient() as client: - response = await client.get("https://httpbin.org/get") - return response.json() -``` - -Vorteil: - -- Viele I/O-lastige Requests können parallel abgewickelt werden. -- Besonders sinnvoll bei Microservices, die viel mit anderen Services kommunizieren. - ---- - -### 3.4 Abhängigkeiten und Wiederverwendbarkeit (Dependency Injection) - -FastAPI bietet ein eingebautes **Dependency Injection**-System. -Beim Entwickeln von APIs brauchst du häufig: - -- Datenbankverbindungen, -- Authentifizierungslogik, -- Konfigurationsobjekte. - -Ohne System würdest du das überall wiederholen oder global speichern. -Mit FastAPI: - -```python -from fastapi import Depends, FastAPI - -app = FastAPI() - -def get_settings(): - # z. B. Konfiguration laden - return {"app_name": "Meine App"} - -@app.get("/info") -def read_info(settings = Depends(get_settings)): - return {"app_name": settings["app_name"]} -``` - -Das verbessert: - -- Testbarkeit (du kannst Dependencies im Test austauschen), -- Struktur deines Codes (klarere Trennung von Zuständigkeiten). - ---- - -## 4. Typische Einsatzszenarien (praxisnah) - -### Beispiel 1: Einfaches CRUD für ein „Item“ - -```python -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel -from typing import List - -app = FastAPI() - -class Item(BaseModel): - id: int - name: str - price: float - -# „Fake-Datenbank“ im Speicher -items_db: List[Item] = [] - -@app.post("/items", response_model=Item) -def create_item(item: Item): - # einfache Prüfung: ID darf nicht doppelt sein - if any(existing.id == item.id for existing in items_db): - raise HTTPException(status_code=400, detail="Item ID already exists") - items_db.append(item) - return item - -@app.get("/items", response_model=List[Item]) -def list_items(): - return items_db - -@app.get("/items/{item_id}", response_model=Item) -def get_item(item_id: int): - for item in items_db: - if item.id == item_id: - return item - raise HTTPException(status_code=404, detail="Item not found") -``` - -Du bekommst: - -- JSON-APIs für CRUD, -- automatische Dokumentation, -- automatische Validierung für `Item`. - ---- - -### Beispiel 2: Path-Parameter, Query-Parameter, Body - -```python -from fastapi import FastAPI -from pydantic import BaseModel -from typing import Optional - -app = FastAPI() - -class SearchFilters(BaseModel): - min_price: Optional[float] = None - max_price: Optional[float] = None - -@app.get("/products/{category}") -def search_products( - category: str, - q: Optional[str] = None, # Query-Parameter ?q=Text - filters: SearchFilters = None # Body JSON -): - return { - "category": category, - "query": q, - "filters": filters - } -``` - -Beispiel-Request: - -- `GET /products/books?q=python` mit JSON-Body: - ```json - { - "min_price": 10, - "max_price": 50 - } - ``` - -FastAPI erkennt: - -- `category` als Pfadparameter, -- `q` als Query-Parameter, -- `filters` als JSON-Body und validiert ihn. - ---- - -### Beispiel 3: Einfache Authentifizierung per Token - -```python -from fastapi import Depends, FastAPI, HTTPException, Header - -app = FastAPI() - -def get_current_user(x_token: str = Header(...)): - if x_token != "secrettoken123": - raise HTTPException(status_code=401, detail="Invalid or missing token") - return {"username": "alice"} - -@app.get("/profile") -def read_profile(current_user = Depends(get_current_user)): - return {"message": f"Hello, {current_user['username']}"} -``` - -Hier: - -- Der Endpunkt `/profile` verlangt einen HTTP-Header `X-Token`. -- FastAPI übernimmt das Zusammenspiel von Header → Dependency → Endpoint-Logik. - ---- - -## 5. Herausforderungen und typische Stolpersteine - -FastAPI nimmt dir viel ab, aber es gibt einige Themen, die für Einsteiger Hürden sein können: - -### 5.1 Asynchrones Programmieren (`async` / `await`) - -- Wenn du noch nie mit Async gearbeitet hast, ist es ungewohnt: - - Wann nutze ich `async def`? - - Wo brauche ich `await`? - - Was ist „Blocking I/O“? -- Du musst darauf achten, dass du **asynchrone Bibliotheken** verwendest, wenn du im Handler `async` einsetzt - (z. B. `httpx` statt `requests`, `asyncpg` statt `psycopg2`). - -Wenn du erst mal einsteigst, kannst du auch erst **synchron** (ohne `async`) starten und später umstellen. - ---- - -### 5.2 Typannotationen und Pydantic verstehen - -FastAPI baut stark auf Typen auf: - -- Für jemanden ohne Erfahrung mit Typannotationen in Python ist das anfangs ungewohnt. -- Du musst verstehen: - - Wie du eigene Modelle mit `BaseModel` definierst. - - Wie optionale Felder mit `Optional[...]` und Standardwerten funktionieren. - - Wie Validierung und Fehlernachrichten von [[Pydantic]] aussehen. - -Aber: -Der Lerneffekt lohnt sich, weil du insgesamt saubereren, stabileren Code bekommst. - ---- - -### 5.3 Datenbankintegration - -FastAPI selbst bringt keinen [[ORM]] mit. Du musst wählen: - -- z. B. **[[SQLAlchemy]]**, Tortoise-ORM, Prisma, Gino etc. - -Dabei stellen sich Fragen wie: - -- Wie verwalte ich Datenbank-Sessions pro Request? -- Nutze ich die sync- oder async-Variante meiner ORM? -- Wie realisiere ich Migrations (alembic, etc.)? - -Es gibt viele Beispielprojekte, aber es ist ein zusätzlicher Schritt im Vergleich zu Django, wo ein [[ORM]] „eingebaut“ ist. - ---- - -### 5.4 Deployment / Betrieb - -Für Einsteiger ist der Weg von „läuft lokal“ zu „läuft im Internet“ oft herausfordernd: - -- FastAPI-Anwendung läuft typischerweise mit: - - [[uvicorn]] oder [[hypercorn]] (ASGI-Server), - - oft hinter einem Reverse Proxy wie [[Nginx]]. -- Themen: - - Logging konfigurieren, - - Umgebungsvariablen (Konfiguration), - - HTTPS/SSL (z. B. via [[Nginx]]/Let’s Encrypt), - - Skalierung (mehrere Worker, z. B. `gunicorn` + `uvicorn.workers.UvicornWorker`). - -Für den Anfang kannst du auch auf Plattformen wie Render, Railway, fly.io, oder Docker + Cloud setzen. - ---- - -### 5.5 Versionierung und Wartung großer Projekte - -Bei größeren APIs: - -- Wie strukturiere ich meinen Code? - - z. B. mit **Routern** (`APIRouter`) und Modulen. -- Wie versioniere ich die API? (`/v1/user`, `/v2/user`, …) -- Wie halte ich die Dokumentation aktuell? - - FastAPI hilft zwar, aber bei vielen Endpunkten braucht man Konventionen und ggf. zusätzliche Dokumentation. - -Beispiel mit Router: - -```python -from fastapi import FastAPI, APIRouter - -app = FastAPI() -items_router = APIRouter(prefix="/items", tags=["items"]) - -@items_router.get("/") -def list_items(): - return [{"id": 1, "name": "Item 1"}] - -app.include_router(items_router) -``` - -So kannst du größere Projekte modular strukturieren. - ---- - -## 6. Zusammenfassung - -- **FastAPI** ist ein modernes Framework für **Web-APIs in Python**, fokussiert auf: - - hohe **Performance** (async), - - **Typen** + automatische Validierung ([[Pydantic]]), - - automatische **OpenAPI-/Swagger-Dokumentation**, - - gute Developer Experience. - -- Es unterscheidet sich von: - - **Flask**: moderner, stärker typisiert, async-first, integrierte Validierung & Doku. - - **Django**: kein Full-Stack-Framework, sondern eher API-fokussiert; du kombinierst es mit eigenen Tools für DB, Templates etc. - - Node/Express: andere Sprache, andere Ökosysteme; FastAPI besonders stark, wenn du ohnehin Python nutzt. - -- Es löst typische Probleme beim API-Bau: - - Validierung von Eingaben, - - Dokumentation & Testbarkeit, - - Performance bei vielen gleichzeitigen Anfragen, - - saubere Struktur durch Dependency Injection. - -- Herausforderungen: - - Einstieg in asynchrones Programmieren, - - Verständnis von Typannotationen & [[Pydantic]], - - separate Auswahl & Integration einer Datenbanklösung, - - Deployment & Betrieb. diff --git a/notes/nextcloud/Notes/IT-Know-How/python/Pydantic.md b/notes/nextcloud/Notes/IT-Know-How/python/Pydantic.md deleted file mode 100755 index 7106bdd..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/Pydantic.md +++ /dev/null @@ -1,470 +0,0 @@ -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). \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/SQLAlchemy.md b/notes/nextcloud/Notes/IT-Know-How/python/SQLAlchemy.md deleted file mode 100755 index 3da02a0..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/SQLAlchemy.md +++ /dev/null @@ -1,1170 +0,0 @@ -SQLAlchemy ist eines der zentralen Datenbank-Tools im Python-Ökosystem. Es bietet sowohl ein mächtiges ORM als auch ein flexibles, SQL-nahes Core-API. - -Im Folgenden: - -1. Grundidee von SQLAlchemy -2. Abgrenzung zu verwandten Ansätzen/Frameworks -3. Welche Probleme SQLAlchemy löst -4. Typische Herausforderungen / Stolpersteine -5. Praxisnahe Beispiele (Core & ORM) - ---- - -## 1. Grundsätzliche Idee von SQLAlchemy - -### Zwei große Säulen: Core und [[ORM]] - -**a) SQLAlchemy Core** - -- Bietet ein **relationales Abstraktions-Framework** nahe an SQL. -- Du definierst Tabellen und baust SQL-Ausdrücke mit Python-Objekten, statt Strings zu schreiben. -- Fokus: **Kontrolle über SQL**, Portabilität zwischen Datenbanken, aber kein “Objekt-Graph” wie beim ORM. - -Beispiel: Tabelle und Query mit Core: - -```python -from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select - -engine = create_engine("sqlite:///example.db", echo=True) -metadata = MetaData() - -users = Table( - "users", - metadata, - Column("id", Integer, primary_key=True), - Column("name", String), - Column("email", String), -) - -metadata.create_all(engine) - -with engine.connect() as conn: - # Insert - conn.execute(users.insert().values(name="Alice", email="alice@example.com")) - conn.commit() - - # Select - stmt = select(users).where(users.c.name == "Alice") - result = conn.execute(stmt) - for row in result: - print(row.id, row.name, row.email) -``` - -**b) SQLAlchemy ORM** - -- Setzt auf dem Core auf und bietet: - - **Objekt-Relationales Mapping**: Tabellen ↔ Python-Klassen. - - **Unit-of-Work / Session**: Änderungen an Objekten werden gesammelt und in Transaktionen geschrieben. - - Navigation über **Beziehungen** (z.B. `user.addresses` statt Joins manuell zu schreiben). - -Beispiel: ORM-Modelle und Nutzung: - -```python -from sqlalchemy import create_engine, Integer, String, ForeignKey -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session - -engine = create_engine("sqlite:///example.db", echo=True) - - -class Base(DeclarativeBase): - pass - - -class User(Base): - __tablename__ = "users" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - name: Mapped[str] = mapped_column(String) - email: Mapped[str] = mapped_column(String) - - addresses: Mapped[list["Address"]] = relationship(back_populates="user") - - -class Address(Base): - __tablename__ = "addresses" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - email_address: Mapped[str] = mapped_column(String) - user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) - - user: Mapped[User] = relationship(back_populates="addresses") - - -Base.metadata.create_all(engine) - -# Arbeiten mit Objekten -with Session(engine) as session: - user = User(name="Bob", email="bob@example.com") - user.addresses.append(Address(email_address="bob@work.com")) - session.add(user) - session.commit() - - # Query - bob = session.query(User).filter_by(name="Bob").first() - print(bob.email, [addr.email_address for addr in bob.addresses]) -``` - -**Kernidee:** -SQLAlchemy bietet dir die Wahl: - -- Du willst **präzises, datenbanknahes SQL**? Nimm **Core**. -- Du willst **Objekte** und **Abstraktion** über SQL? Nimm das **ORM** (intern nutzt es Core). - ---- - -## 2. Abgrenzung zu ähnlichen Vorgehensweisen / Paketen / Paradigmen - -### a) Raw SQL / DB-API (z.B. mit `psycopg2`, `sqlite3`) - -- Du schreibst SQL-Strings direkt: - ```python - import sqlite3 - - conn = sqlite3.connect("example.db") - cursor = conn.cursor() - cursor.execute("SELECT id, name FROM users WHERE name = ?", ("Alice",)) - rows = cursor.fetchall() - ``` -- **Vorteile**: - - Vollständige Kontrolle, keine zusätzliche Abstraktion. - - Einfach für sehr kleine Projekte / Skripte. -- **Nachteile**: - - Manuelles Mappen in Python-Objekte. - - Kein einheitliches, portables Abstraktionslevel. - - Redundante String-SQLs; schwer wartbar bei großen Codebasen. - -**SQLAlchemy Core** liegt hier zwischen „Raw SQL” und „ORM”: -- Bietet Typsicherheit (bis zu einem gewissen Grad), Query-Building, Portabilität. -- Keine Magic, aber viel Komfort. - -### b) Andere ORMs (z.B. Django ORM, peewee, ponyORM) - -**Django ORM:** - -- Stark mit Django-Framework integriert, nicht generisch. -- Typisch: `Model.objects.filter(...)`. -- **SQLAlchemy vs Django ORM**: - - SQLAlchemy ist **Framework-agnostisch**, kann in [[FastAPI]], Flask, CLI-Skripten etc. genutzt werden. - - ORM-Funktionen sind **umfangreicher** (z.B. komplexes Relationship-Handling, polymorphe Mappings, etc.). - - Django ORM ist einfacher für typische Web-CRUD-Anwendungen, weniger flexibel bei exotischer DB-Logik. - -**peewee, ponyORM etc.:** - -- Meist leichtergewichtig, weniger boilerplate. -- Oft weniger mächtig in Sonderfällen (komplexe Joins, exotische DB-Features). -- SQLAlchemy ist tendenziell der „Power-User-Ansatz”: breit, tief, konfigurierbar. - -### c) Active Record vs Data Mapper - -- Viele ORMs (Rails ActiveRecord, Django) folgen dem **Active-Record-Pattern**: - - Das Model-Objekt kennt seine Datenzugriffe (`save()`, `delete()`). -- SQLAlchemy ORM folgt eher dem **Data-Mapper-Pattern**: - - Domain-Objekte sind relativ „rein“. - - Persistenz wird über die Session/Mapper hergestellt. -- Folge: stärkere **Trennung von Domänenlogik und Persistence** – gut für komplexe Business-Logik. - ---- - -## 3. Welche Probleme werden gelöst? - -### a) Impedance Mismatch: Objektwelt vs Relationale Welt - -- Python: Objekte, Vererbung, Beziehungen über Attribute/Listen. -- Datenbank: Tabellen, Fremdschlüssel, Joins, Normalisierung. - -SQLAlchemy ORM löst u.a.: - -- **Mapping von Tabellen auf Klassen** -- **Mapping von Zeilen auf Objekte** -- **Beziehungen als Python-Attribute** (`user.addresses` statt manueller Join). -- **Vererbung**: Single-Table-Inheritance, Joined-Table-Inheritance etc. - -Beispiel-Vererbung (Joined-Table): - -```python -class Employee(Base): - __tablename__ = "employees" - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String) - type: Mapped[str] = mapped_column(String) # Discriminator - - __mapper_args__ = { - "polymorphic_on": type, - "polymorphic_identity": "employee", - } - - -class Manager(Employee): - __tablename__ = "managers" - id: Mapped[int] = mapped_column(ForeignKey("employees.id"), primary_key=True) - department: Mapped[str] = mapped_column(String) - - __mapper_args__ = {"polymorphic_identity": "manager"} -``` - -### b) Transaktionen und Unit-of-Work - -- Die **Session** verwaltet ein Set von Objekten: - - Änderungen werden verfolgt (dirty tracking). - - `session.commit()` bündelt alles in einer Transaktion. -- Du musst nicht jede Insert/Update/Delete-Anweisung selbst schreiben. - -Beispiel: - -```python -with Session(engine) as session: - user = session.get(User, 1) - user.email = "new@example.com" # Änderung am Objekt - session.commit() # ORM generiert UPDATE users SET email=... WHERE id=1 -``` - -### c) Portabilität zwischen Datenbanken - -- Einmal definierte Modelle funktionieren (meistens) auf SQLite, PostgreSQL, MySQL, Oracle usw. -- Migration von einer DB-Engine zur anderen ist deutlich einfacher, solange du SQL-Standard-Funktionalitäten nutzt. -- Im Zusammenspiel mit **Alembic** (Migrations-Tool von SQLAlchemy) sind Schema-Änderungen integrierbar. - -### d) Komplexe Queries in kontrollierbarer Form - -- SQLAlchemy Core/ORM erlaubt den **systematischen Aufbau** von SQL-Ausdrücken: - - Zusammensetzen von Filtern / Joins / Subqueries abhängig von Parametern zur Laufzeit. - - Vermeidet Fehler bei String-SQL-Konkatenation. - -Beispiel: dynamischer Query-Aufbau mit ORM: - -```python -from sqlalchemy import select, or_ - -def search_users(session: Session, name=None, email=None): - stmt = select(User) - conditions = [] - if name: - conditions.append(User.name.ilike(f"%{name}%")) - if email: - conditions.append(User.email.ilike(f"%{email}%")) - - if conditions: - stmt = stmt.where(or_(*conditions)) - - return session.scalars(stmt).all() -``` - ---- - -## 4. Herausforderungen und typische Stolpersteine - -### a) Lernkurve und Komplexität - -- SQLAlchemy ist **umfangreich**: - - Core, ORM, Mappings, Loader-Options, Eager/Lazy Loading, Events, etc. -- Für einfache CRUD-Apps wirkt es am Anfang „overkill“. -- Viele Wege führen zum Ziel (Core vs ORM, Declarative vs klassische Mappings, Query-APIs). - -**Praxis-Tipp:** -Für neue Projekte: -- Starte mit **Declarative ORM** und einfachen Patterns. -- Lern bei Bedarf zusätzlich Core für spezielle SQL-Fälle. - -### b) Session-Management und Lebenszyklus - -Falscher Umgang mit Sessions ist eine häufige Fehlerquelle: - -- Zu lange lebende Sessions, die viele Objekte halten → hoher Memory-Footprint. -- Session in Web-Apps: - - Typischer Pattern: **“Session-per-request”**: - - Am Anfang eines Requests Session erstellen. - - Am Ende commit/rollback und schließen. - -Beispiel mit [[FastAPI]]: - -```python -from fastapi import Depends, FastAPI -from sqlalchemy.orm import Session - -app = FastAPI() - -def get_session(): - with Session(engine) as session: - yield session - -@app.get("/users/{user_id}") -def read_user(user_id: int, session: Session = Depends(get_session)): - return session.get(User, user_id) -``` - -### c) Lazy Loading vs Eager Loading - -- Beziehungen sind per Default oft **lazy geladen**: - - Zugriff auf `user.addresses` löst eine neue Query aus. -- Probleme: - - In Schleifen kann das zu **N+1 Query Problem** führen. -- Lösung: - - Eager Loading über Optionen (`joinedload`, `selectinload`). - -Beispiel Eager Loading: - -```python -from sqlalchemy.orm import joinedload - -with Session(engine) as session: - users = ( - session.query(User) - .options(joinedload(User.addresses)) - .all() - ) - - # Kein weiterer Query für .addresses nötig - for user in users: - print(user.name, len(user.addresses)) -``` - -### d) Performance-Tuning - -- ORM-Komfort kann SQL „verstecken“ – was gut und schlecht sein kann: - - Es können ineffiziente Queries entstehen, ohne dass du es sofort merkst. -- Tools: - - `echo=True` im Engine zum SQL-Loggen. - - Profiler, EXPLAIN-Analyse in der DB. -- Manchmal ist es besser, für komplexe Performance-kritische Queries: - - **Explizites Core-Statement** oder sogar **Raw SQL** zu verwenden. - -### e) Migrations / Schema-Änderungen - -- SQLAlchemy selbst bietet **keine Migrationen**, das ist die Aufgabe von **Alembic**. -- Herausforderung: - - Konsistentes Management von Modellen (Python) und Schema (DB). - - Migration-Skripte schreiben, testen, deployen. - -Trotzdem ist das Zusammenspiel SQLAlchemy + Alembic ein de-facto Standard in vielen Projekten. - ---- - -## 5. Praxisnahe Beispiele - -### a) Typisches Setup in einer kleinen Web-Anwendung - -Struktur: -- `database.py` – Engine und Session-Factory -- `models.py` – ORM-Modelle -- `crud.py` – Datenbank-Operationen -- `main.py` – FastAPI/Flask-Endpoints - -`database.py`: - -```python -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -DATABASE_URL = "sqlite:///./app.db" - -engine = create_engine(DATABASE_URL, echo=False, future=True) -SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) -``` - -`models.py`: - -```python -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from sqlalchemy import Integer, String - -class Base(DeclarativeBase): - pass - -class User(Base): - __tablename__ = "users" - - id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) - name: Mapped[str] = mapped_column(String, index=True) - email: Mapped[str] = mapped_column(String, unique=True, index=True) -``` - -`crud.py`: - -```python -from sqlalchemy.orm import Session -from .models import User - -def get_user_by_email(db: Session, email: str) -> User | None: - return db.query(User).filter(User.email == email).first() - -def create_user(db: Session, name: str, email: str) -> User: - user = User(name=name, email=email) - db.add(user) - db.commit() - db.refresh(user) - return user -``` - -`main.py` (FastAPI): - -```python -from fastapi import FastAPI, Depends -from sqlalchemy.orm import Session -from .database import SessionLocal, engine -from .models import Base -from . import crud - -app = FastAPI() -Base.metadata.create_all(bind=engine) - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - -@app.post("/users/") -def create_user(name: str, email: str, db: Session = Depends(get_db)): - return crud.create_user(db, name, email) - -@app.get("/users/{email}") -def read_user(email: str, db: Session = Depends(get_db)): - return crud.get_user_by_email(db, email) -``` - -### b) Mischung aus Core und ORM - -Beispiel: Du nutzt ORM-Models, aber für eine bestimmte, komplexe Aggregation lieber Core: - -```python -from sqlalchemy import select, func -from sqlalchemy.orm import Session -from .models import User - -def count_users_by_first_letter(db: Session): - stmt = ( - select(func.substr(User.name, 1, 1).label("initial"), func.count()) - .group_by("initial") - .order_by("initial") - ) - return db.execute(stmt).all() -``` - ---- - -## Zusammenfassung - -- **Grundidee:** - SQLAlchemy ist ein flexibles Toolkit, das sowohl eine SQL-nahe Abstraktion (Core) als auch ein mächtiges ORM (Data-Mapper-basiert) bereitstellt. Es überbrückt den Graben zwischen Objektwelt (Python) und relationalen Datenbanken. - -- **Abgrenzung:** - Im Vergleich zu Raw SQL: mehr Komfort, Typsicherheit, Portabilität. - Im Vergleich zu anderen ORMs: framework-agnostisch, sehr flexibel, eher für komplexere Anforderungen optimiert. - -- **Gelöste Probleme:** - Objekt-relationales Mapping, Transaktionsverwaltung (Unit-of-Work), DB-Portabilität, systematischer Query-Aufbau, Integration mit Migrations-Tooling (Alembic). - -- **Herausforderungen:** - Relativ hohe Lernkurve, Session-Handling, Lazy/Eager Loading, Performance-Tuning, Zusammenspiel mit Migrationen. -# # Data Management With Python, SQLite, and SQLAlchemy -[Data Management With Python, SQLite, and SQLAlchemy – Real Python](https://realpython.com/python-sqlite-sqlalchemy/) - -All programs process data in one form or another, and many need to be able to save and retrieve that data from one invocation to the next. Python, [SQLite](https://www.sqlite.org/index.html), and [SQLAlchemy](https://www.sqlalchemy.org/) give your programs database functionality, allowing you to store data in a single file without the need for a database server. - -You can achieve similar results using [flat files](https://en.wikipedia.org/wiki/Flat-file_database) in any number of formats, including CSV, JSON, XML, and even custom formats. Flat files are often human-readable text files—though they can also be binary data—with a structure that can be parsed by a computer program. Below, you’ll explore using SQL databases and flat files for data storage and manipulation and learn how to decide which approach is right for your program. - -**In this tutorial, you’ll learn how to use:** - -- **Flat files** for data storage -- **SQL** to improve access to persistent data -- **SQLite** for data storage -- **SQLAlchemy** to work with data as Python objects - -You can get all of the code and data you’ll see in this tutorial by clicking on the link below: - -**Download the sample code:** [Click here to get the code you’ll use](https://realpython.com/bonus/sqlite-sqlalchemy-code/) to learn about data management with SQLite and SQLAlchemy in this tutorial. - -## Using Flat Files for Data Storage[](https://realpython.com/python-sqlite-sqlalchemy/#using-flat-files-for-data-storage "Permanent link") - -A **flat file** is a file containing data with no internal hierarchy and usually no references to external files. Flat files contain human-readable characters and are very useful for creating and reading data. Because they don’t have to use fixed field widths, flat files often use other structures to make it possible for a program to parse text. - -For example, [comma-separated value (CSV)](https://realpython.com/python-csv/) files are lines of plain text in which the comma character separates the data elements. Each line of text represents a row of data, and each comma-separated value is a field within that row. The comma character delimiter indicates the boundary between data values. - -Python excels at [reading from and saving to files](https://realpython.com/read-write-files-python/). Being able to read data files with Python allows you to restore an application to a useful state when you rerun it at a later time. Being able to save data in a file allows you to share information from the program between users and sites where the application runs. - -Before a program can read a data file, it has to be able to understand the data. Usually, this means the data file needs to have some structure that the application can use to read and parse the text in the file. - -Below is a CSV file named `author_book_publisher.csv`, used by the first example program in this tutorial: - -`first_name,last_name,title,publisher Isaac,Asimov,Foundation,Random House Pearl,Buck,The Good Earth,Random House Pearl,Buck,The Good Earth,Simon & Schuster Tom,Clancy,The Hunt For Red October,Berkley Tom,Clancy,Patriot Games,Simon & Schuster Stephen,King,It,Random House Stephen,King,It,Penguin Random House Stephen,King,Dead Zone,Random House Stephen,King,The Shining,Penguin Random House John,Le Carre,"Tinker, Tailor, Soldier, Spy: A George Smiley Novel",Berkley Alex,Michaelides,The Silent Patient,Simon & Schuster Carol,Shaben,Into The Abyss,Simon & Schuster` - -The first line provides a comma-separated list of fields, which are the column names for the data that follows in the remaining lines. The rest of the lines contain the data, with each line representing a single record. - -**Note:** Though the authors, books, and publishers are all real, the relationships between books and publishers are fictional and were created for the purposes of this tutorial. - -Next, you’ll take a look at some of the advantages and disadvantages of using flat files like the above CSV to work with your data. - -[Remove ads](https://realpython.com/account/join/) - -### Advantages of Flat Files[](https://realpython.com/python-sqlite-sqlalchemy/#advantages-of-flat-files "Permanent link") - -Working with data in flat files is manageable and straightforward to implement. Having the data in a human-readable format is helpful not only for creating the data file with a text editor but also for examining the data and looking for any inconsistencies or problems. - -Many applications can export flat-file versions of the data generated by the file. For example, [Excel](https://realpython.com/openpyxl-excel-spreadsheets-python/) can import or export a CSV file to and from a spreadsheet. Flat files also have the advantage of being self-contained and transferable if you want to share the data. - -Almost every programming language has tools and libraries that make working with CSV files easier. Python has the built-in `csv` module and the powerful [pandas](https://realpython.com/pandas-read-write-files/) module available, making working with CSV files a potent solution. - -### Disadvantages of Flat Files[](https://realpython.com/python-sqlite-sqlalchemy/#disadvantages-of-flat-files "Permanent link") - -The advantages of working with flat files start to diminish as the data becomes larger. Large files are still human-readable, but editing them to create data or look for problems becomes a more difficult task. If your application will change the data in the file, then one solution would be to [read the entire file into memory](https://realpython.com/read-write-files-python/), make the changes, and write the data out to another file. - -Another problem with using flat files is that you’ll need to explicitly create and maintain any relationships between parts of your data and the application program within the file syntax. Additionally, you’ll need to generate code in your application to use those relationships. - -A final complication is that people you want to share your data file with will also need to know about and act on the structures and relationships you’ve created in the data. To access the information, those users will need to understand not only the structure of the data but also the programming tools necessary for accessing it. - -### Flat File Example[](https://realpython.com/python-sqlite-sqlalchemy/#flat-file-example "Permanent link") - -The example program `examples/example_1/main.py` uses the `author_book_publisher.csv` file to get the data and relationships in it. This CSV file maintains a list of authors, the books they’ve published, and the publishers for each of the books. - -**Note:** The data files used in the examples are available in the `project/data` directory. There’s also a program file in the `project/build_data` directory that generates the data. That application is useful if you change the data and want to get back to a known state. - -To get access to the data files used in this section and throughout the tutorial, click on the link below: - -**Download the sample code:** [Click here to get the code you’ll use](https://realpython.com/bonus/sqlite-sqlalchemy-code/) to learn about data management with SQLite and SQLAlchemy in this tutorial. - -The CSV file presented above is a pretty small data file containing only a few authors, books, and publishers. You should also notice some things about the data: - -- The authors Stephen King and Tom Clancy appear more than once because multiple books they’ve published are represented in the data. - -- The authors Stephen King and Pearl Buck have the same book published by more than one publisher. - - -These duplicated data fields create relationships between other parts of the data. One author can write many books, and one publisher can work with multiple authors. Authors and publishers share relationships with individual books. - -The relationships in the `author_book_publisher.csv` file are represented by fields that appear multiple times in different rows of the data file. Because of this data redundancy, the data represents more than a single two-dimensional table. You’ll see more of this when you use the file to create an SQLite database file. - -The example program `examples/example_1/main.py` uses the relationships embedded in the `author_book_publisher.csv` file to generate some data. It first presents a list of the authors and the number of books each has written. It then shows a list of publishers and the number of authors for which each has published books. - -It also uses the [`treelib`](https://treelib.readthedocs.io/en/latest/) module to display a tree hierarchy of the authors, books , and publishers. - -Lastly, it adds a new book to the data and redisplays the tree hierarchy with the new book in place. Here’s the [`main()`](https://realpython.com/courses/python-main-function/) entry-point function for this program: - -`def main(): """The main entry point of the program""" # Get the resources for the program with resources.path( "project.data", "author_book_publisher.csv" ) as filepath: data = get_data(filepath) # Get the number of books printed by each publisher books_by_publisher = get_books_by_publisher(data, ascending=False) for publisher, total_books in books_by_publisher.items(): print(f"Publisher: {publisher}, total books: {total_books}") print() # Get the number of authors each publisher publishes authors_by_publisher = get_authors_by_publisher(data, ascending=False) for publisher, total_authors in authors_by_publisher.items(): print(f"Publisher: {publisher}, total authors: {total_authors}") print() # Output hierarchical authors data output_author_hierarchy(data) # Add a new book to the data structure data = add_new_book( data, author_name="Stephen King", book_title="The Stand", publisher_name="Random House", ) # Output the updated hierarchical authors data output_author_hierarchy(data)` - -The Python code above takes the following steps: - -- **Lines 4 to 7** read the `author_book_publisher.csv` file into a pandas DataFrame. -- **Lines 10 to 13** print the number of books published by each publisher. -- **Lines 16 to 19** print the number of authors associated with each publisher. -- **Line 22** outputs the book data as a hierarchy sorted by authors. -- **Lines 25 to 30** add a new book to the in-memory structure. -- **Line 33** outputs the book data as a hierarchy sorted by authors, including the newly added book. - -Running this program generates the following output: - -`$ python main.py Publisher: Simon & Schuster, total books: 4 Publisher: Random House, total books: 4 Publisher: Penguin Random House, total books: 2 Publisher: Berkley, total books: 2 Publisher: Simon & Schuster, total authors: 4 Publisher: Random House, total authors: 3 Publisher: Berkley, total authors: 2 Publisher: Penguin Random House, total authors: 1 Authors ├── Alex Michaelides │ └── The Silent Patient │ └── Simon & Schuster ├── Carol Shaben │ └── Into The Abyss │ └── Simon & Schuster ├── Isaac Asimov │ └── Foundation │ └── Random House ├── John Le Carre │ └── Tinker, Tailor, Soldier, Spy: A George Smiley Novel │ └── Berkley ├── Pearl Buck │ └── The Good Earth │ ├── Random House │ └── Simon & Schuster ├── Stephen King │ ├── Dead Zone │ │ └── Random House │ ├── It │ │ ├── Penguin Random House │ │ └── Random House │ └── The Shining │ └── Penguin Random House └── Tom Clancy ├── Patriot Games │ └── Simon & Schuster └── The Hunt For Red October └── Berkley` - -The author hierarchy above is presented twice in the output, with the addition of Stephen King’s _The Stand_, published by Random House. The actual output above has been edited and shows only the first hierarchy output to save space. - -`main()` calls other functions to perform the bulk of the work. The first function it calls is `get_data()`: - -`def get_data(filepath): """Get book data from the csv file""" return pd.read_csv(filepath)` - -This function takes in the file path to the CSV file and uses pandas to read it into a [pandas DataFrame](https://realpython.com/pandas-dataframe/), which it then passes back to the caller. The return value of this function becomes the data structure passed to the other functions that make up the program. - -`get_books_by_publisher()` calculates the number of books published by each publisher. The resulting pandas [Series](https://realpython.com/pandas-python-explore-dataset/#understanding-series-objects) uses the pandas [GroupBy](https://realpython.com/pandas-groupby/) functionality to group by publisher and then [sort](https://realpython.com/pandas-sort-python/) based on the `ascending` flag: - -`def get_books_by_publisher(data, ascending=True): """Return the number of books by each publisher as a pandas series""" return data.groupby("publisher").size().sort_values(ascending=ascending)` - -`get_authors_by_publisher()` does essentially the same thing as the previous function, but for authors: - -`def get_authors_by_publisher(data, ascending=True): """Returns the number of authors by each publisher as a pandas series""" return ( data.assign(name=data.first_name.str.cat(data.last_name, sep=" ")) .groupby("publisher") .nunique() .loc[:, "name"] .sort_values(ascending=ascending) )` - -`add_new_book()` creates a new book in the pandas DataFrame. The code checks to see if the author, book, or publisher already exists. If not, then it creates a new book and appends it to the pandas DataFrame: - -`def add_new_book(data, author_name, book_title, publisher_name): """Adds a new book to the system""" # Does the book exist? first_name, _, last_name = author_name.partition(" ") if any( (data.first_name == first_name) & (data.last_name == last_name) & (data.title == book_title) & (data.publisher == publisher_name) ): return data # Add the new book return data.append( { "first_name": first_name, "last_name": last_name, "title": book_title, "publisher": publisher_name, }, ignore_index=True, )` - -`output_author_hierarchy()` uses nested [`for` loops](https://realpython.com/python-for-loop/) to iterate through the levels of the data structure. It then uses the `treelib` module to output a hierarchical listing of the authors, the books they’ve published, and the publishers who’ve published those books: - -`def output_author_hierarchy(data): """Output the data as a hierarchy list of authors""" authors = data.assign( name=data.first_name.str.cat(data.last_name, sep=" ") ) authors_tree = Tree() authors_tree.create_node("Authors", "authors") for author, books in authors.groupby("name"): authors_tree.create_node(author, author, parent="authors") for book, publishers in books.groupby("title")["publisher"]: book_id = f"{author}:{book}" authors_tree.create_node(book, book_id, parent=author) for publisher in publishers: authors_tree.create_node(publisher, parent=book_id) # Output the hierarchical authors data authors_tree.show()` - -This application works well and illustrates the power available to you with the pandas module. The module provides excellent functionality for reading a CSV file and interacting with the data. - -Let’s push on and create an identically functioning program using Python, an SQLite database version of the author and publication data, and SQLAlchemy to interact with that data. - -[Remove ads](https://realpython.com/account/join/) - -## Using SQLite to Persist Data[](https://realpython.com/python-sqlite-sqlalchemy/#using-sqlite-to-persist-data "Permanent link") - -As you saw earlier, there’s redundant data in the `author_book_publisher.csv` file. For example, all information about Pearl Buck’s _The Good Earth_ is listed twice because two different publishers have published the book. - -Imagine if this data file contained more related data, like the author’s address and phone number, publication dates and ISBNs for books, or addresses, phone numbers, and perhaps yearly revenue for publishers. This data would be duplicated for each root data item, like author, book, or publisher. - -It’s possible to create data this way, but it would be exceptionally unwieldy. Think about the problems keeping this data file current. What if [Stephen King wanted to change his name](https://en.wikipedia.org/wiki/Richard_Bachman)? You’d have to update multiple records containing his name and make sure there were no typos. - -Worse than the data duplication would be the complexity of adding other relationships to the data. What if you decided to add phone numbers for the authors, and they had phone numbers for home, work, mobile, and perhaps more? Every new relationship that you’d want to add for any root item would multiply the number of records by the number of items in that new relationship. - -This problem is one reason that relationships exist in database systems. An important topic in database engineering is **database normalization**, or the process of breaking apart data to reduce redundancy and increase integrity. When a database structure is extended with new types of data, having it normalized beforehand keeps changes to the existing structure to a minimum. - -The SQLite database is available in Python, and according to the [SQLite home page](https://www.sqlite.org/index.html), it’s used more than all other database systems combined. It offers a full-featured [relational database management system (RDBMS)](https://en.wikipedia.org/wiki/Relational_database#Relations_or_tables) that works with a single file to maintain all the database functionality. - -It also has the advantage of not requiring a separate database server to function. The database file format is cross-platform and accessible to any programming language that supports SQLite. - -All of this is interesting information, but how is it relevant to the use of flat files for data storage? You’ll find out below! - -### Creating a Database Structure[](https://realpython.com/python-sqlite-sqlalchemy/#creating-a-database-structure "Permanent link") - -The brute force approach to getting the `author_book_publisher.csv` data into an SQLite database would be to create a single table matching the structure of the CSV file. Doing this would ignore a good deal of SQLite’s power. - -**Relational databases** provide a way to store structured data in tables and establish relationships between those tables. They usually use [Structured Query Language (SQL)](https://en.wikipedia.org/wiki/SQL) as the primary way to interact with the data. This is an oversimplification of what RDBMSs provide, but it’s sufficient for the purposes of this tutorial. - -An SQLite database provides support for interacting with the data table using SQL. Not only does an SQLite database file contain the data, but it also has a standardized way to interact with the data. This support is embedded in the file, meaning that any programming language that can use an SQLite file can also use SQL to work with it. - -### Interacting With a Database With SQL[](https://realpython.com/python-sqlite-sqlalchemy/#interacting-with-a-database-with-sql "Permanent link") - -SQL is a **declarative language** used to create, manage, and query the data contained in a database. A declarative language describes _what_ is to be accomplished rather than _how_ it should be accomplished. You’ll see examples of SQL statements later when you get to creating database tables. - -## Structuring a Database With SQL[](https://realpython.com/python-sqlite-sqlalchemy/#structuring-a-database-with-sql "Permanent link") - -To take advantage of the power in SQL, you’ll need to apply some database normalization to the data in the `author_book_publisher.csv` file. To do this, you’ll separate the authors, books, and publishers into separate database tables. - -Conceptually, data is stored in the database in two-dimensional table structures. Each table consists of rows of **records**, and each record consists of columns, or **fields**, containing data. - -The data contained in the fields is of pre-defined types, including text, [integers, floats](https://realpython.com/python-numbers/#integers-and-floating-point-numbers), and more. CSV files are different because all the fields are text and must be parsed by a program to have a data type assigned to them. - -Each record in the table has a **primary key** defined to give a record a unique identifier. The primary key is similar to the key in a [Python dictionary](https://realpython.com/python-dicts/). The database engine itself often generates the primary key as an incrementing integer value for every record inserted into the database table. - -Though the primary key is often automatically generated by the database engine, it doesn’t have to be. If the data stored in a field is unique across all other data in the table in that field, then it can be the primary key. For example, a table containing data about books could use the book’s ISBN as the primary key. - -[Remove ads](https://realpython.com/account/join/) - -### Creating Tables With SQL[](https://realpython.com/python-sqlite-sqlalchemy/#creating-tables-with-sql "Permanent link") - -Here’s how you can create the three tables representing the authors, books, and publishers in the CSV file using SQL statements: - -`CREATE TABLE author ( author_id INTEGER NOT NULL PRIMARY KEY, first_name VARCHAR, last_name VARCHAR ); CREATE TABLE book ( book_id INTEGER NOT NULL PRIMARY KEY, author_id INTEGER REFERENCES author, title VARCHAR ); CREATE TABLE publisher ( publisher_id INTEGER NOT NULL PRIMARY KEY, name VARCHAR );` - -Notice there are no file operations, no variables created, and no structures to hold them. The statements describe only the desired result: the creation of a table with particular attributes. The database engine determines how to do this. - -Once you’ve created and populated this table with author data from the `author_book_publisher.csv` file, you can access it using SQL statements. The following statement (also called a **query**) uses the wildcard character (`*`) to get all the data in the `author` table and output it: - -`SELECT * FROM author;` - -You can use the [`sqlite3`](https://sqlite.org/cli.html) command-line tool to interact with the `author_book_publisher.db` database file in the `project/data` directory: - -`$ sqlite3 author_book_publisher.db` - -Once the SQLite command-line tool is running with the database open, you can enter SQL commands. Here’s the above SQL command and its output, followed by the `.q` command to exit the program: - -`sqlite> SELECT * FROM author; 1|Isaac|Asimov 2|Pearl|Buck 3|Tom|Clancy 4|Stephen|King 5|John|Le Carre 6|Alex|Michaelides 7|Carol|Shaben sqlite> .q` - -Notice that each author exists only once in the table. Unlike the CSV file, which had multiple entries for some of the authors, here, only one unique record per author is necessary. - -### Maintaining a Database With SQL[](https://realpython.com/python-sqlite-sqlalchemy/#maintaining-a-database-with-sql "Permanent link") - -SQL provides ways to work with existing databases and tables by inserting new data and updating or deleting existing data. Here’s an example SQL statement for inserting a new author into the `author` table: - -`INSERT INTO author (first_name, last_name) VALUES ('Paul', 'Mendez');` - -This SQL statement inserts the values ‘`Paul`’ and ‘`Mendez`’ into the respective columns `first_name` and `last_name` of the `author` table. - -Notice that the `author_id` column isn’t specified. Because that column is the primary key, the database engine generates the value and inserts it as part of the statement execution. - -Updating records in a database table is an uncomplicated process. For instance, suppose Stephen King wanted to be known by his pen name, Richard Bachman. Here’s an the SQL statement to update the database record: - -`UPDATE author SET first_name = 'Richard', last_name = 'Bachman' WHERE first_name = 'Stephen' AND last_name = 'King';` - -The SQL statement locates the single record for `'Stephen King'` using the conditional statement `WHERE first_name = 'Stephen' AND last_name = 'King'` and then updates the `first_name` and `last_name` fields with the new values. SQL uses the equals sign (`=`) as both the comparison operator and [assignment operator](https://realpython.com/python-assignment-operator/). - -You can also delete records from a database. Here’s an example SQL statement to delete a record from the `author` table: - -`DELETE FROM author WHERE first_name = 'Paul' AND last_name = 'Mendez';` - -This SQL statement deletes a single row from the `author` table where the `first_name` is equal to `'Paul'` and the `last_name` is equal to `'Mendez'`. - -Be careful when deleting records! The conditions you set must be as specific as possible. A conditional that’s too broad can lead to deleting more records than you intend. For example, if the condition were based only on the line `first_name = 'Paul'`, then all authors with a first name of Paul would be deleted from the database. - -**Note:** To avoid the accidental deletion of records, many applications don’t allow deletions at all. Instead, the record has another column to indicate if it’s in use or not. This column might be named `active` and contain a value that evaluates to either True or False, indicating whether the record should be included when querying the database. - -For example, the SQL query below would get all columns for all active records in `some_table`: - -`SELECT * FROM some_table WHERE active = 1;` - -SQLite doesn’t have a [Boolean data type](https://realpython.com/python-boolean/), so the `active` column is represented by an integer with a value of `0` or `1` to indicate the state of the record. Other database systems may or may not have native Boolean data types. - -It’s entirely possible to build database applications in Python using SQL statements directly in the code. Doing so returns data to the application as a list of [lists](https://realpython.com/python-lists-tuples/) or list of [dictionaries](https://realpython.com/courses/dictionaries-python/). - -Using raw SQL is a perfectly acceptable way to work with the data returned by queries to the database. However, rather than doing that, you’re going to move directly into using SQLAlchemy to work with databases. - -[Remove ads](https://realpython.com/account/join/) - -## Building Relationships[](https://realpython.com/python-sqlite-sqlalchemy/#building-relationships "Permanent link") - -Another feature of database systems that you might find even more powerful and useful than data persistence and retrieval is **relationships**. Databases that support relationships allow you to break up data into multiple tables and establish connections between them. - -The data in the `author_book_publisher.csv` file represents the data and relationships by duplicating data. A database handles this by breaking the data up into three tables—`author`, `book`, and `publisher`—and establishing relationships between them. - -After getting all the data you want into one place in the CSV file, why would you want to break it up into multiple tables? Wouldn’t it be more work to create and put back together again? That’s true to an extent, but the advantages of breaking up the data and putting it back together using SQL could win you over! - -### One-to-Many Relationships[](https://realpython.com/python-sqlite-sqlalchemy/#one-to-many-relationships "Permanent link") - -A **one-to-many** relationship is like that of a customer ordering items online. One customer can have many orders, but each order belongs to one customer. The `author_book_publisher.db` database has a one-to-many relationship in the form of authors and books. Each author can write many books, but each book is written by one author. - -As you saw in the table creation above, the implementation of these separate entities is to place each into a database table, one for authors and one for books. But how does the one-to-many relationship between these two tables get implemented? - -Remember, each table in a database has a field designated as the primary key for that table. Each table above has a primary key field named using this pattern: `_id`. - -The `book` table shown above contains a field, `author_id`, that references the `author` table. The `author_id` field establishes a one-to-many relationship between authors and books that looks like this: - -[![ERD diagram for the author_book relationship produced with JetBrains DataGrip application](https://files.realpython.com/media/author_book.e9c86f34967d.png)](https://files.realpython.com/media/author_book.e9c86f34967d.png) - -The diagram above is a simple [entity-relationship diagram (ERD)](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model) created with the [JetBrains DataGrip](https://www.jetbrains.com/datagrip/features/) application showing the tables `author` and `book` as boxes with their respective primary key and data fields. Two graphical items add information about the relationship: - -1. **The small yellow and blue key icons** indicate the primary and foreign keys for the table, respectively. - -2. **The arrow connecting `book` to `author`** indicates the relationship between the tables based on the `author_id` foreign key in the `book` table. - - -When you add a new book to the `book` table, the data includes an `author_id` value for an existing author in the `author` table. In this way, all the books written by an author have a lookup relationship back to that unique author. - -Now that you have separate tables for authors and books, how do you use the relationship between them? SQL supports what’s called a [`JOIN`](https://realpython.com/python-sql-libraries/#join) operation, which you can use to tell the database how to connect two or more tables. - -The SQL query below joins the `author` and `book` table together using the SQLite command-line application: - -`sqlite> SELECT ...> a.first_name || ' ' || a.last_name AS author_name, ...> b.title AS book_title ...> FROM author a ...> JOIN book b ON b.author_id = a.author_id ...> ORDER BY a.last_name ASC; Isaac Asimov|Foundation Pearl Buck|The Good Earth Tom Clancy|The Hunt For Red October Tom Clancy|Patriot Games Stephen King|It Stephen King|Dead Zone Stephen King|The Shining John Le Carre|Tinker, Tailor, Soldier, Spy: A George Smiley Novel Alex Michaelides|The Silent Patient Carol Shaben|Into The Abyss` - -The SQL query above gathers information from both the author and book table by joining the tables using the relationship established between the two. SQL [string concatenation](https://realpython.com/python-string-concatenation/) assigns the author’s full name to the alias `author_name`. The data gathered by the query are sorted in ascending order by the `last_name` field. - -There are a few things to notice in the SQL statement. First, authors are presented by their full names in a single column and sorted by their last names. Also, authors appear in the output multiple times because of the one-to-many relationship. An author’s name is duplicated for each book they’ve written in the database. - -By creating separate tables for authors and books and establishing the relationship between them, you’ve reduced redundancy in the data. Now you only have to edit an author’s data in one place, and that change appears in any SQL query accessing the data. - -### Many-to-Many Relationships[](https://realpython.com/python-sqlite-sqlalchemy/#many-to-many-relationships "Permanent link") - -**Many-to-many** relationships exist in the `author_book_publisher.db` database between authors and publishers as well as between books and publishers. One author can work with many publishers, and one publisher can work with many authors. Similarly, one book can be published by many publishers, and one publisher can publish many books. - -Handling this situation in the database is more involved than a one-to-many relationship because the relationship goes both ways. Many-to-many relationships are created by an **association table** acting as a bridge between the two related tables. - -The association table contains at least two foreign key fields, which are the primary keys of each of the two associated tables. This SQL statement creates the association table relating the `author` and `publisher` tables: - -`CREATE TABLE author_publisher ( author_id INTEGER REFERENCES author, publisher_id INTEGER REFERENCES publisher );` - -The SQL statements create a new `author_publisher` table referencing the primary keys of the existing `author` and `publisher` tables. The `author_publisher` table is an association table establishing relationships between an author and a publisher. - -Because the relationship is between two primary keys, there’s no need to create a primary key for the association table itself. The combination of the two related keys creates a unique identifier for a row of data. - -As before, you use the `JOIN` keyword to connect two tables together. Connecting the `author` table to the `publisher` table is a two-step process: - -1. `JOIN` the `author` table with the `author_publisher` table. -2. `JOIN` the `author_publisher` table with the `publisher` table. - -The `author_publisher` association table provides the bridge through which the `JOIN` connects the two tables. Here’s an example SQL query returning a list of authors and the publishers publishing their books: - -`sqlite> SELECT ...> a.first_name || ' ' || a.last_name AS author_name, ...> p.name AS publisher_name ...> FROM author a ...> JOIN author_publisher ap ON ap.author_id = a.author_id ...> JOIN publisher p ON p.publisher_id = ap.publisher_id ...> ORDER BY a.last_name ASC; Isaac Asimov|Random House Pearl Buck|Random House Pearl Buck|Simon & Schuster Tom Clancy|Berkley Tom Clancy|Simon & Schuster Stephen King|Random House Stephen King|Penguin Random House John Le Carre|Berkley Alex Michaelides|Simon & Schuster Carol Shaben|Simon & Schuster` - -The statements above perform the following actions: - -- **Line 1** starts a `SELECT` statement to get data from the database. - -- **Line 2** selects the first and last name from the `author` table using the `a` alias for the `author` table and concatenates them together with a space character. - -- **Line 3** selects the publisher’s name aliased to `publisher_name`. - -- **Line 4** uses the `author` table as the first source from which to retrieve data and assigns it to the alias `a`. - -- **Line 5** is the first step of the process outlined above for connecting the `author` table to the `publisher` table. It uses the alias `ap` for the `author_publisher` association table and performs a `JOIN` operation to connect the `ap.author_id` foreign key reference to the `a.author_id` primary key in the `author` table. - -- **Line 6** is the second step in the two-step process mentioned above. It uses the alias `p` for the `publisher` table and performs a `JOIN` operation to relate the `ap.publisher_id` foreign key reference to the `p.publisher_id` primary key in the `publisher` table. - -- **Line 7** sorts the data by the author’s last name in ascending alphabetical order and ends the SQL query. - -- **Lines 8 to 17** are the output of the SQL query. - - -Note that the data in the source `author` and `publisher` tables are normalized, with no data duplication. Yet the returned results have duplicated data where necessary to answer the SQL query. - -The SQL query above demonstrates how to make use of a relationship using the SQL `JOIN` keyword, but the resulting data is a partial re-creation of the `author_book_publisher.csv` CSV data. What’s the win for having done the work of creating a database to separate the data? - -Here’s another SQL query to show a little bit of the power of SQL and the database engine: - -`sqlite> SELECT ...> a.first_name || ' ' || a.last_name AS author_name, ...> COUNT(b.title) AS total_books ...> FROM author a ...> JOIN book b ON b.author_id = a.author_id ...> GROUP BY author_name ...> ORDER BY total_books DESC, a.last_name ASC; Stephen King|3 Tom Clancy|2 Isaac Asimov|1 Pearl Buck|1 John Le Carre|1 Alex Michaelides|1 Carol Shaben|1` - -The SQL query above returns the list of authors and the number of books they’ve written. The list is sorted first by the number of books in descending order, then by the author’s name in alphabetical order: - -- **Line 1** begins the SQL query with the `SELECT` keyword. - -- **Line 2** selects the author’s first and last names, separated by a space character, and creates the alias `author_name`. - -- **Line 3** counts the number of books written by each author, which will be used later by the `ORDER BY` clause to sort the list. - -- **Line 4** selects the `author` table to get data from and creates the `a` alias. - -- **Line 5** connects to the related `book` table through a `JOIN` to the `author_id` and creates the `b` alias for the `book` table. - -- **Line 6** generates the aggregated author and total number of books data by using the `GROUP BY` keyword. `GROUP BY` is what groups each `author_name` and controls what books are tallied by `COUNT()` for that author. - -- **Line 7** sorts the output first by number of books in descending order, then by the author’s last name in ascending alphabetical order. - -- **Lines 8 to 14** are the output of the SQL query. - - -In the above example, you take advantage of SQL to perform aggregation calculations and sort the results into a useful order. Having the database perform calculations based on its built-in data organization ability is usually faster than performing the same kinds of calculations on raw data sets in Python. SQL offers the advantages of using [set theory](https://www.sqlshack.com/mathematics-sql-server-fast-introduction-set-theory/) embedded in RDBMS databases. - -[Remove ads](https://realpython.com/account/join/) - -### Entity Relationship Diagrams[](https://realpython.com/python-sqlite-sqlalchemy/#entity-relationship-diagrams "Permanent link") - -An [entity-relationship diagram (ERD)](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model) is a visual depiction of an entity-relationship model for a database or part of a database. The `author_book_publisher.db` SQLite database is small enough that the entire database can be visualized by the diagram shown below: - -[![ERD diagram for the author_book_publisher Sqlite database produced with JetBrains DataGrip application](https://files.realpython.com/media/author_book_publisher.fbc88687deeb.png)](https://files.realpython.com/media/author_book_publisher.fbc88687deeb.png) - -This diagram presents the table structures in the database and the relationships between them. Each box represents a table and contains the fields defined in the table, with the primary key indicated first if it exists. - -The arrows show the relationships between the tables connecting a foreign key field in one table to a field, often the primary key, in another table. The table `book_publisher` has two arrows, one connecting it to the `book` table and another connecting it to the `publisher` table. The arrow indicates the many-to-many relationship between the `book` and `publisher` tables. The `author_publisher` table provides the same relationship between `author` and `publisher`. - -## Working With SQLAlchemy and Python Objects[](https://realpython.com/python-sqlite-sqlalchemy/#working-with-sqlalchemy-and-python-objects "Permanent link") - -[SQLAlchemy](https://www.sqlalchemy.org/) is a powerful database access tool kit for Python, with its [object-relational mapper (ORM)](https://en.wikipedia.org/wiki/Object-relational_mapping) being one of its most famous components, and the one discussed and used here. - -When you’re working in an [object-oriented](https://realpython.com/python3-object-oriented-programming/) language like Python, it’s often useful to think in terms of objects. It’s possible to map the results returned by SQL queries to objects, but doing so works against the grain of how the database works. Sticking with the scalar results provided by SQL works against the grain of how Python developers work. This problem is known as [object-relational impedance mismatch](https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch). - -The ORM provided by SQLAlchemy sits between the SQLite database and your Python program and transforms the data flow between the database engine and Python objects. SQLAlchemy allows you to think in terms of objects and still retain the powerful features of a database engine. - -### The Model[](https://realpython.com/python-sqlite-sqlalchemy/#the-model "Permanent link") - -One of the fundamental elements to enable connecting SQLAlchemy to a database is creating a **model**. The model is a [Python class](https://realpython.com/python-classes/) defining the data mapping between the Python objects returned as a result of a database query and the underlying database tables. - -The entity-relationship diagram displayed earlier shows boxes connected with arrows. The boxes are the tables built with the SQL commands and are what the Python classes will model. The arrows are the relationships between the tables. - -The models are Python classes inheriting from an SQLAlchemy `Base` class. The `Base` class provides the interface operations between instances of the model and the database table. - -Below is the `models.py` file that creates the models to represent the `author_book_publisher.db` database: - -`from sqlalchemy import Column, Integer, String, ForeignKey, Table from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() author_publisher = Table( "author_publisher", Base.metadata, Column("author_id", Integer, ForeignKey("author.author_id")), Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")), ) book_publisher = Table( "book_publisher", Base.metadata, Column("book_id", Integer, ForeignKey("book.book_id")), Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")), ) class Author(Base): __tablename__ = "author" author_id = Column(Integer, primary_key=True) first_name = Column(String) last_name = Column(String) books = relationship("Book", backref=backref("author")) publishers = relationship( "Publisher", secondary=author_publisher, back_populates="authors" ) class Book(Base): __tablename__ = "book" book_id = Column(Integer, primary_key=True) author_id = Column(Integer, ForeignKey("author.author_id")) title = Column(String) publishers = relationship( "Publisher", secondary=book_publisher, back_populates="books" ) class Publisher(Base): __tablename__ = "publisher" publisher_id = Column(Integer, primary_key=True) name = Column(String) authors = relationship( "Author", secondary=author_publisher, back_populates="publishers" ) books = relationship( "Book", secondary=book_publisher, back_populates="publishers" )` - -Here’s what’s going on in this module: - -- **Line 1** imports the `Column`, `Integer`, `String`, `ForeignKey`, and `Table` classes from SQLAlchemy, which are used to help define the model attributes. - -- **Line 2** imports the `relationship()` and `backref` objects, which are used to create the relationships between objects. - -- **Line 3** imports the `declarative_base` object, which connects the database engine to the SQLAlchemy functionality of the models. - -- **Line 5** creates the `Base` class, which is what all models inherit from and how they get SQLAlchemy ORM functionality. - -- **Lines 7 to 12** create the `author_publisher` association table model. - -- **Lines 14 to 19** create the `book_publisher` association table model. - -- **Lines 21 to 29** define the `Author` class model to the `author` database table. - -- **Lines 31 to 38** define the `Book` class model to the `book` database table. - -- **Lines 40 to 49** define the `Publisher` class model to the `publisher` database table. - - -The description above shows the mapping of the five tables in the `author_book_publisher.db` database. But it glosses over some SQLAlchemy ORM features, including `Table`, `ForeignKey`, `relationship()`, and `backref`. Let’s get into those now. - -### `Table` Creates Associations[](https://realpython.com/python-sqlite-sqlalchemy/#table-creates-associations "Permanent link") - -`author_publisher` and `book_publisher` are both instances of the `Table` class that create the many-to-many association tables used between the `author` and `publisher` tables and the `book` and `publisher` tables, respectively. - -The SQLAlchemy `Table` class creates a unique instance of an ORM mapped table within the database. The first parameter is the table name as defined in the database, and the second is `Base.metadata`, which provides the connection between the SQLAlchemy functionality and the database engine. - -The rest of the parameters are instances of the `Column` class defining the table fields by name, their type, and in the example above, an instance of a `ForeignKey`. - -[Remove ads](https://realpython.com/account/join/) - -### `ForeignKey` Creates a Connection[](https://realpython.com/python-sqlite-sqlalchemy/#foreignkey-creates-a-connection "Permanent link") - -The SQLAlchemy **`ForeignKey`** class defines a dependency between two `Column` fields in different tables. A `ForeignKey` is how you make SQLAlchemy aware of the relationships between tables. For example, this line from the `author_publisher` instance creation establishes a foreign key relationship: - -`Column("author_id", Integer, ForeignKey("author.author_id"))` - -The statement above tells SQLAlchemy that there’s a column in the `author_publisher` table named `author_id`. The type of that column is `Integer`, and `author_id` is a foreign key related to the primary key in the `author` table. - -Having both `author_id` and `publisher_id` defined in the `author_publisher` `Table` instance creates the connection from the `author` table to the `publisher` table and vice versa, establishing a many-to-many relationship. - -### `relationship()` Establishes a Collection[](https://realpython.com/python-sqlite-sqlalchemy/#relationship-establishes-a-collection "Permanent link") - -Having a `ForeignKey` defines the existence of the relationship between tables but not the collection of books an author can have. Take a look at this line in the `Author` class definition: - -`books = relationship("Book", backref=backref("author"))` - -The code above defines a parent-child collection. The `books` attribute being plural (which is not a requirement, just a convention) is an indication that it’s a collection. - -The first parameter to `relationship()`, the class name `Book` (which is _not_ the table name `book`), is the class to which the `books` attribute is related. The `relationship` informs SQLAlchemy that there’s a relationship between the `Author` and `Book` classes. SQLAlchemy will find the relationship in the `Book` class definition: - -`author_id = Column(Integer, ForeignKey("author.author_id"))` - -SQLAlchemy recognizes that this is the `ForeignKey` connection point between the two classes. You’ll get to the `backref` parameter in `relationship()` in a moment. - -The other relationship in `Author` is to the `Publisher` class. This is created with the following statement in the `Author` class definition: - -`publishers = relationship( "Publisher", secondary=author_publisher, back_populates="authors" )` - -Like `books`, the attribute `publishers` indicates a collection of publishers associated with an author. The first parameter, `"Publisher"`, informs SQLAlchemy what the related class is. The second and third parameters are `secondary=author_publisher` and `back_populates="authors"`: - -- **`secondary`** tells SQLAlchemy that the relationship to the `Publisher` class is through a secondary table, which is the `author_publisher` association table created earlier in `models.py`. The `secondary` parameter makes SQLAlchemy find the `publisher_id` `ForeignKey` defined in the `author_publisher` association table. - -- **`back_populates`** is a convenience configuration telling SQLAlchemy that there’s a complementary collection in the `Publisher` class called `authors`. - - -### `backref` Mirrors Attributes[](https://realpython.com/python-sqlite-sqlalchemy/#backref-mirrors-attributes "Permanent link") - -The **`backref`** parameter of the `books` collection `relationship()` creates an `author` attribute for each `Book` instance. This attribute refers to the parent `Author` that the `Book` instance is related to. - -For example, if you executed the following Python code, then a `Book` instance would be returned from the SQLAlchemy query. The `Book` instance has attributes that can be used to print out information about the book: - -`book = session.query(Book).filter_by(Book.title == "The Stand").one_or_none() print(f"Authors name: {book.author.first_name} {book.author.last_name}")` - -The existence of the `author` attribute in the `Book` above is because of the `backref` definition. A `backref` can be very handy to have when you need to refer to the parent and all you have is a child instance. - -[Remove ads](https://realpython.com/account/join/) - -### Queries Answer Questions[](https://realpython.com/python-sqlite-sqlalchemy/#queries-answer-questions "Permanent link") - -You can make a basic query like `SELECT * FROM author;` in SQLAlchemy like this: - -`results = session.query(Author).all()` - -The **`session`** is an SQLAlchemy object used to communicate with SQLite in the Python example programs. Here, you tell the session you want to execute a query against the `Author` model and return all records. - -At this point, the advantages of using SQLAlchemy instead of plain SQL might not be obvious, especially considering the setup required to create the models representing the database. The `results` returned by the query is where the magic happens. Instead of getting back a list of lists of scalar data, you’ll get back a list of instances of `Author` objects with attributes matching the column names you defined. - -The `books` and `publishers` collections maintained by SQLAlchemy create a hierarchical list of authors and the books they’ve written as well as the publishers who’ve published them. - -Behind the scenes, SQLAlchemy turns the object and method calls into SQL statements to execute against the SQLite database engine. SQLAlchemy transforms the data returned by SQL queries into Python objects. - -With SQLAlchemy, you can perform the more complex aggregation query shown earlier for the list of authors and the number of books they’ve written like this: - -`author_book_totals = ( session.query( Author.first_name, Author.last_name, func.count(Book.title).label("book_total") ) .join(Book) .group_by(Author.last_name) .order_by(desc("book_total")) .all() )` - -The query above gets the author’s first and last name, along with a count of the number of books that the author has written. The aggregating `count` used by the `group_by` clause is based on the author’s last name. Finally, the results are sorted in descending order based on the aggregated and aliased `book_total`. - -### Example Program[](https://realpython.com/python-sqlite-sqlalchemy/#example-program "Permanent link") - -The example program `examples/example_2/main.py` has the same functionality as `examples/example_1/main.py` but uses SQLAlchemy exclusively to interface with the `author_book_publisher.db` SQLite database. The program is broken up into the `main()` function and the functions it calls: - -`def main(): """Main entry point of program""" # Connect to the database using SQLAlchemy with resources.path( "project.data", "author_book_publisher.db" ) as sqlite_filepath: engine = create_engine(f"sqlite:///{sqlite_filepath}") Session = sessionmaker() Session.configure(bind=engine) session = Session() # Get the number of books printed by each publisher books_by_publisher = get_books_by_publishers(session, ascending=False) for row in books_by_publisher: print(f"Publisher: {row.name}, total books: {row.total_books}") print() # Get the number of authors each publisher publishes authors_by_publisher = get_authors_by_publishers(session) for row in authors_by_publisher: print(f"Publisher: {row.name}, total authors: {row.total_authors}") print() # Output hierarchical author data authors = get_authors(session) output_author_hierarchy(authors) # Add a new book add_new_book( session, author_name="Stephen King", book_title="The Stand", publisher_name="Random House", ) # Output the updated hierarchical author data authors = get_authors(session) output_author_hierarchy(authors)` - -This program is a modified version of `examples/example_1/main.py`. Let’s go over the differences: - -- **Lines 4 to 7** first initialize the `sqlite_filepath` variable to the database file path. Then they create the `engine` variable to communicate with SQLite and the `author_book_publisher.db` database file, which is SQLAlchemy’s access point to the database. - -- **Line 8** creates the `Session` class from the SQLAlchemy’s `sessionmaker()`. - -- **Line 9** binds the `Session` to the engine created in line 8. - -- **Line 10** creates the `session` instance, which is used by the program to communicate with SQLAlchemy. - - -The rest of the function is similar, except for the replacement of `data` with `session` as the first parameter to all the functions called by `main()`. - -`get_books_by_publisher()` has been refactored to use SQLAlchemy and the models you defined earlier to get the data requested: - -`def get_books_by_publishers(session, ascending=True): """Get a list of publishers and the number of books they've published""" if not isinstance(ascending, bool): raise ValueError(f"Sorting value invalid: {ascending}") direction = asc if ascending else desc return ( session.query( Publisher.name, func.count(Book.title).label("total_books") ) .join(Publisher.books) .group_by(Publisher.name) .order_by(direction("total_books")) )` - -Here’s what the new function, `get_books_by_publishers()`, is doing: - -- **Line 6** creates the `direction` variable and sets it equal to the SQLAlchemy `desc` or `asc` function depending on the value of the `ascending` parameter. - -- **Lines 9 to 11** query the `Publisher` table for data to return, which in this case are `Publisher.name` and the aggregate total of `Book` objects associated with an author, aliased to `total_books`. - -- **Line 12** joins to the `Publisher.books` collection. - -- **Line 13** aggregates the book counts by the `Publisher.name` attribute. - -- **Line 14** sorts the output by the book counts according to the operator defined by `direction`. - -- **Line 15** closes the object, executes the query, and returns the results to the caller. - - -All the above code expresses what is wanted rather than how it’s to be retrieved. Now instead of using SQL to describe what’s wanted, you’re using Python objects and methods. What’s returned is a list of Python objects instead of a list of tuples of data. - -`get_authors_by_publisher()` has also been modified to work exclusively with SQLAlchemy. Its functionality is very similar to the previous function, so a function description is omitted: - -`def get_authors_by_publishers(session, ascending=True): """Get a list of publishers and the number of authors they've published""" if not isinstance(ascending, bool): raise ValueError(f"Sorting value invalid: {ascending}") direction = asc if ascending else desc return ( session.query( Publisher.name, func.count(Author.first_name).label("total_authors"), ) .join(Publisher.authors) .group_by(Publisher.name) .order_by(direction("total_authors")) )` - -`get_authors()` has been added to get a list of authors sorted by their last names. The result of this query is a list of `Author` objects containing a collection of books. The `Author` objects already contain hierarchical data, so the results don’t have to be reformatted: - -`def get_authors(session): """Get a list of author objects sorted by last name""" return session.query(Author).order_by(Author.last_name).all()` - -Like its previous version, `add_new_book()` is relatively complex but straightforward to understand. It determines if a book with the same title, author, and publisher exists in the database already. - -If the search query finds an exact match, then the function returns. If no book matches the exact search criteria, then it searches to see if the author has written a book using the passed in title. This code exists to prevent duplicate books from being created in the database. - -If no matching book exists, and the author hasn’t written one with the same title, then a new book is created. The function then retrieves or creates an author and publisher. Once instances of the `Book`, `Author` and `Publisher` exist, the relationships between them are created, and the resulting information is saved to the database: - -`def add_new_book(session, author_name, book_title, publisher_name): """Adds a new book to the system""" # Get the author's first and last names first_name, _, last_name = author_name.partition(" ") # Check if book exists book = ( session.query(Book) .join(Author) .filter(Book.title == book_title) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .filter(Book.publishers.any(Publisher.name == publisher_name)) .one_or_none() ) # Does the book by the author and publisher already exist? if book is not None: return # Get the book by the author book = ( session.query(Book) .join(Author) .filter(Book.title == book_title) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .one_or_none() ) # Create the new book if needed if book is None: book = Book(title=book_title) # Get the author author = ( session.query(Author) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .one_or_none() ) # Do we need to create the author? if author is None: author = Author(first_name=first_name, last_name=last_name) session.add(author) # Get the publisher publisher = ( session.query(Publisher) .filter(Publisher.name == publisher_name) .one_or_none() ) # Do we need to create the publisher? if publisher is None: publisher = Publisher(name=publisher_name) session.add(publisher) # Initialize the book relationships book.author = author book.publishers.append(publisher) session.add(book) # Commit to the database session.commit()` - -The code above is relatively long. Let’s break the functionality down to manageable sections: - -- **Lines 7 to 18** set the `book` variable to an instance of a `Book` if a book with the same title, author, and publisher is found. Otherwise, they set `book` to `None`. - -- **Lines 20 and 21** determine if the book already exists and return if it does. - -- **Lines 24 to 37** set the `book` variable to an instance of a `Book` if a book with the same title and author is found. Otherwise, they create a new `Book` instance. - -- **Lines 40 to 52** set the `author` variable to an existing author, if found, or create a new `Author` instance based on the passed-in author name. - -- **Lines 55 to 63** set the `publisher` variable to an existing publisher, if found, or create a new `Publisher` instance based on the passed-in publisher name. - -- **Line 66** sets the `book.author` instance to the `author` instance. This creates the relationship between the author and the book, which SQLAlchemy will create in the database when the session is committed. - -- **Line 67** adds the `publisher` instance to the `book.publishers` collection. This creates the many-to-many relationship between the `book` and `publisher` tables. SQLAlchemy will create references in the tables as well as in the `book_publisher` association table that connects the two. - -- **Line 68** adds the `Book` instance to the session, making it part of the session’s unit of work. - -- **Line 71** commits all the creations and updates to the database. - - -There are a few things to take note of here. First, there’s is no mention of the `author_publisher` or `book_publisher` association tables in either the queries or the creations and updates. Because of the work you did in `models.py` setting up the relationships, SQLAlchemy can handle connecting objects together and keeping those tables in sync during creations and updates. - -Second, all the creations and updates happen within the context of the `session` object. None of that activity is touching the database. Only when the `session.commit()` statement executes does the session then go through its [unit of work](https://www.martinfowler.com/eaaCatalog/unitOfWork.html) and commit that work to the database. - -For example, if a new `Book` instance is created (as in line 37 above), then the book has its attributes initialized except for the `book_id` primary key and `author_id` foreign key. Because no database activity has happened yet, the `book_id` is unknown, and nothing was done in the instantiation of `book` to give it an `author_id`. - -When `session.commit()` is executed, one of the things it will do is insert `book` into the database, at which point the database will create the `book_id` primary key. The session will then initialize the `book.book_id` value with the primary key value created by the database engine. - -`session.commit()` is also aware of the insertion of the `Book` instance in the `author.books` collection. The `author` object’s `author_id` primary key will be added to the `Book` instance appended to the `author.books` collection as the `author_id` foreign key. - -[Remove ads](https://realpython.com/account/join/) - -## Providing Access to Multiple Users[](https://realpython.com/python-sqlite-sqlalchemy/#providing-access-to-multiple-users "Permanent link") - -To this point, you’ve seen how to use pandas, SQLite, and SQLAlchemy to access the same data in different ways. For the relatively straightforward use case of the author, book, and publisher data, it could still be a toss-up whether you should use a database. - -One deciding factor when choosing between using a flat file or a database is data and relationship complexity. If the data for each entity is complicated and contains many relationships between the entities, then creating and maintaining it in a flat file might become more difficult. - -Another factor to consider is whether you want to share the data between multiple users. The solution to this problem might be as simple as using a [sneakernet](https://en.wikipedia.org/wiki/Sneakernet) to physically move data between users. Moving data files around this way has the advantage of ease of use, but the data can quickly get out of sync when changes are made. - -The problem of keeping the data consistent for all users becomes even more difficult if the users are remote and want to access the data across networks. Even when you’re limited to a single language like Python and using pandas to access the data, network file locking isn’t sufficient to ensure the data doesn’t get corrupted. - -Providing the data through a server application and a user interface alleviates this problem. The server is the only application that needs file-level access to the database. By using a database, the server can take advantage of SQL to access the data using a consistent interface no matter what programming language the server uses. - -The last example program demonstrates this by providing a web application and user interface to the [Chinook](https://www.sqlitetutorial.net/sqlite-sample-database/) sample SQLite database. Peter Stark generously maintains the Chinook database as part of the [SQLite Tutorial](https://www.sqlitetutorial.net/) site. If you’d like to learn more about SQLite and SQL in general, then the site is a great resource. - -The Chinook database provides artist, music, and playlist information along the lines of a simplified [Spotify](https://www.spotify.com/). The database is part of the example code project in the `project/data` folder. - -## Using Flask With Python, SQLite, and SQLAlchemy[](https://realpython.com/python-sqlite-sqlalchemy/#using-flask-with-python-sqlite-and-sqlalchemy "Permanent link") - -The `examples/example_3/chinook_server.py` program creates a [Flask](https://flask.palletsprojects.com/en/1.1.x/) application that you can interact with using a browser. The application makes use of the following technologies: - -- [**Flask Blueprint**](https://flask.palletsprojects.com/en/1.1.x/blueprints/) is part of Flask and provides a good way to follow the [separation of concerns](https://realpython.com/flask-blueprint/) design principle and create distinct modules to contain functionality. - -- [**Flask SQLAlchemy**](https://pypi.org/project/Flask-SQLAlchemy/) is an extension for Flask that adds support for SQLAlchemy in your web applications. - -- [**Flask_Bootstrap4**](https://pypi.org/project/Flask-Bootstrap4/) packages the [Bootstrap](https://getbootstrap.com/) front-end tool kit, integrating it with your Flask web applications. - -- [**Flask_WTF**](https://pypi.org/project/Flask-WTF/) extends Flask with [WTForms](https://wtforms.readthedocs.io/en/2.3.x/), giving your web applications a useful way to generate and validate web forms. - -- [**python_dotenv**](https://pypi.org/project/python-dotenv/) is a Python module that an application uses to read environment variables from a file and keep sensitive information out of program code. - - -Though not necessary for this example, a `.env` file holds the environment variables for the application. The `.env` file exists to contain sensitive information like passwords, which you should keep out of your code files. However, the content of the project `.env` file is shown below since it doesn’t contain any sensitive data: - -`SECRET_KEY = "you-will-never-guess" SQLALCHEMY_TRACK_MODIFICATIONS = False SQLAlCHEMY_ECHO = False DEBUG = True` - -The example application is fairly large, and only some of it is relevant to this tutorial. For this reason, examining and learning from the code is left as an exercise for the reader. That said, you can take a look at an animated screen capture of the application below, followed by the HTML that renders the home page and the Python Flask route that provides the dynamic data. - -Here’s the application in action, navigating through various menus and features: - -[![The chinook database web application in action as an animated GIF](https://files.realpython.com/media/python-sqlite-sqlalchemy-in-action.8fcee355cc31.gif)](https://files.realpython.com/media/python-sqlite-sqlalchemy-in-action.8fcee355cc31.gif) - -The animated screen capture starts on the application home page, styled using [Bootstrap 4](https://getbootstrap.com/). The page displays the artists in the database, sorted in ascending order. The remainder of the screen capture presents the results of clicking on the displayed links or navigating around the application from the top-level menu. - -Here’s the [Jinja2](https://realpython.com/primer-on-jinja-templating/) HTML template that generates the home page of the application: - -`{% extends "base.html" %} {% block content %}
Create New Artist
{{ form.csrf_token }} {{ render_field(form.name, placeholder=form.name.label.text) }}
{% for artist in artists %} {% endfor %}
List of Artists
Artist Name
{{ artist.name }}
{% endblock %}` - -Here’s what’s going on in this Jinja2 template code: - -- **Line 1** uses Jinja2 template inheritance to build this template from the `base.html` template. The `base.html` template contains all the HTML5 boilerplate code as well as the Bootstrap navigation bar consistent across all pages of the site. - -- **Lines 3 to 37** contain the block content of the page, which is incorporated into the Jinja2 macro of the same name in the `base.html` base template. - -- **Lines 9 to 13** render the form to create a new artist. This uses the features of [Flask-WTF](https://pypi.org/project/Flask-WTF/) to generate the form. - -- **Lines 24 to 32** create a `for` loop that renders the table of artist names. - -- **Lines 27 to 29** render the artist name as a link to the artist’s album page showing the songs associated with a particular artist. - - -Here’s the Python route that renders the page: - -`from flask import Blueprint, render_template, redirect, url_for from flask_wtf import FlaskForm from wtforms import StringField from wtforms.validators import InputRequired, ValidationError from app import db from app.models import Artist # Set up the blueprint artists_bp = Blueprint( "artists_bp", __name__, template_folder="templates", static_folder="static" ) def does_artist_exist(form, field): artist = ( db.session.query(Artist) .filter(Artist.name == field.data) .one_or_none() ) if artist is not None: raise ValidationError("Artist already exists", field.data) class CreateArtistForm(FlaskForm): name = StringField( label="Artist's Name", validators=[InputRequired(), does_artist_exist] ) @artists_bp.route("/") @artists_bp.route("/artists", methods=["GET", "POST"]) def artists(): form = CreateArtistForm() # Is the form valid? if form.validate_on_submit(): # Create new artist artist = Artist(name=form.name.data) db.session.add(artist) db.session.commit() return redirect(url_for("artists_bp.artists")) artists = db.session.query(Artist).order_by(Artist.name).all() return render_template("artists.html", artists=artists, form=form,)` - -Let’s go over what the above code is doing: - -- **Lines 1 to 6** import all the modules necessary to render the page and initialize forms with data from the database. - -- **Lines 9 to 11** create the blueprint for the artists page. - -- **Lines 13 to 20** create a custom validator function for the Flask-WTF forms to make sure a request to create a new artist doesn’t conflict with an already existing artist. - -- **Lines 22 to 25** create the form class to handle the artist form rendered in the browser and provide validation of the form field inputs. - -- **Lines 27 to 28** connect two routes to the `artists()` function they decorate. - -- **Line 30** creates an instance of the `CreateArtistForm()` class. - -- **Line 33** determines if the page was requested through the HTTP methods GET or POST (submit). If it was a POST, then it also validates the fields of the form and informs the user if the fields are invalid. - -- **Lines 35 to 37** create a new artist object, add it to the SQLAlchemy session, and commit the artist object to the database, persisting it. - -- **Line 38** redirects back to the artists page, which will be rerendered with the newly created artist. - -- **Line 40** runs an SQLAlchemy query to get all the artists in the database and sort them by `Artist.name`. - -- **Line 41** renders the artists page if the HTTP request method was a GET. - - -You can see that a great deal of functionality is created by a reasonably small amount of code. - -[Remove ads](https://realpython.com/account/join/) - -## Creating a REST API Server[](https://realpython.com/python-sqlite-sqlalchemy/#creating-a-rest-api-server "Permanent link") - -You can also create a web server providing a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. This kind of server offers URL endpoints responding with data, often in [JSON](https://en.wikipedia.org/wiki/JSON) format. A server providing REST API endpoints can be used by JavaScript single-page web applications through the use of AJAX HTTP requests. - -Flask is an excellent tool for creating REST applications. For a multi-part series of tutorials about using Flask, Connexion, and SQLAlchemy to create REST applications, check out [Python REST APIs With Flask, Connexion, and SQLAlchemy](https://realpython.com/flask-connexion-rest-api/). - -If you’re a fan of Django and are interested in creating REST APIs, then check out [Django Rest Framework – An Introduction](https://realpython.com/django-rest-framework-quick-start/) and [Create a Super Basic REST API with Django Tastypie](https://realpython.com/create-a-super-basic-rest-api-with-django-tastypie/). - -**Note:** It’s reasonable to ask if SQLite is the right choice as the database backend to a web application. The [SQLite website](https://www.sqlite.org/whentouse.html) states that SQLite is a good choice for sites that serve around 100,000 hits per day. If your site gets more daily hits, the first thing to say is congratulations! - -Beyond that, if you’ve implemented your website with SQLAlchemy, then it’s possible to move the data from SQLite to another database such as [MySQL](https://realpython.com/python-mysql/) or PostgreSQL. For a comparison of SQLite, MySQL, and PostgreSQL that will help you make decisions about which one will serve your application best, check out [Introduction to Python SQL Libraries](https://realpython.com/python-sql-libraries/). - -It’s well worth considering SQLite for your Python application, no matter what it is. Using a database gives your application versatility, and it might create surprising opportunities to add additional features. - -## Conclusion[](https://realpython.com/python-sqlite-sqlalchemy/#conclusion "Permanent link") - -You’ve covered a lot of ground in this tutorial about databases, SQLite, SQL, and SQLAlchemy! You’ve used these tools to move data contained in flat files to an SQLite database, access the data with SQL and SQLAlchemy, and provide that data through a web server. - -**In this tutorial, you’ve learned:** - -- Why an **SQLite database** can be a compelling alternative to flat-file data storage -- How to **normalize data** to reduce data redundancy and increase data integrity -- How to use **SQLAlchemy** to work with databases in an object-oriented manner -- How to build a **web application** to serve a database to multiple users - -Working with databases is a powerful abstraction for working with data that adds significant functionality to your Python programs and allows you to ask interesting questions of your data. - -You can get all of the code and data you saw in this tutorial at the link below: - -**Download the sample code:** [Click here to get the code you’ll use](https://realpython.com/bonus/sqlite-sqlalchemy-code/) to learn about data management with SQLite and SQLAlchemy in this tutorial. - -## Further Reading[](https://realpython.com/python-sqlite-sqlalchemy/#further-reading "Permanent link") - -This tutorial is an introduction to using databases, SQL, and SQLAlchemy, but there’s much more to learn about these subjects. These are powerful, sophisticated tools that no single tutorial can cover adequately. Here are some resources for additional information to expand your skills: - -- If your application will expose the database to users, then avoiding SQL injection attacks is an important skill. For more information, check out [Preventing SQL Injection Attacks With Python](https://realpython.com/prevent-python-sql-injection/). - -- Providing web access to a database is common in web-based single-page applications. To learn how, check out [Python REST APIs With Flask, Connexion, and SQLAlchemy – Part 2](https://realpython.com/flask-connexion-rest-api-part-2/). - -- Preparing for [data engineering](https://realpython.com/python-data-engineer/) job interviews gives you a leg up in your career. To get started, check out [Data Engineer Interview Questions With Python](https://realpython.com/data-engineer-interview-questions-python/). - -- Migrating data and being able to roll back using Flask with Postgres and SQLAlchemy is an integral part of the Software Development Life Cycle (SDLC). You can learn more about it by checking out [Flask by Example – Setting up Postgres, SQLAlchemy, and Alembic](https://realpython.com/flask-by-example-part-2-postgres-sqlalchemy-and-alembic/). - ---- -# Anhang: Promt -```text -Gib mir eine umfassende und detaillierte Übersicht zum Python Paket "SQLAlchemy". -* Wie ist die grundsätzliche Idee? -* Welche Abgrenzung zu ähnlichen oder verwandten Vorgehensweisen, Paketen oder Pradigmen gibt es -* Welche Probleme werden gelöst? -* Welche Herausforderungen gibt es? -* Ergänze Erläuterungen mit praxisnahen Beispielen. -``` \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/argpase.md b/notes/nextcloud/Notes/IT-Know-How/python/argpase.md deleted file mode 100755 index e86d645..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/argpase.md +++ /dev/null @@ -1,368 +0,0 @@ -## 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() -``` - ---- diff --git a/notes/nextcloud/Notes/IT-Know-How/python/black.md b/notes/nextcloud/Notes/IT-Know-How/python/black.md deleted file mode 100755 index 20c6cec..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/black.md +++ /dev/null @@ -1,525 +0,0 @@ -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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/mypy.md b/notes/nextcloud/Notes/IT-Know-How/python/mypy.md deleted file mode 100755 index e35f98c..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/mypy.md +++ /dev/null @@ -1,640 +0,0 @@ -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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/pytest.md b/notes/nextcloud/Notes/IT-Know-How/python/pytest.md deleted file mode 100755 index e84a7d2..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/pytest.md +++ /dev/null @@ -1,1212 +0,0 @@ -# Chatbot Output -... nach [[#Anhang Prompt|diesem Promt]] - -Hier ein ausführliches, praxisnahes Tutorial zu `pytest`, inkl. Vergleich zu `unittest` und vielen Beispielen. - ---- -## 1. Kurzer Einstieg: Was ist `pytest`? - -`pytest` ist ein beliebtes Test-Framework für Python, das gegenüber `unittest` vor allem: - -- weniger Boilerplate benötigt, -- deutlich mächtigere Fixture-Mechanismen bietet, -- umfangreiche Parametrisierung unterstützt, -- ein großes Plugin-Ökosystem hat. - -### Installation - -```bash -pip install pytest -``` - -### Minimaler Test mit `pytest` - -Datei: `test_example.py` - -```python -def test_addition(): - assert 1 + 1 == 2 -``` - -Ausführen: - -```bash -pytest -``` - -`pytest` findet automatisch alle Dateien, die mit `test_` beginnen oder auf `_test.py` enden, und darin Funktionen/Klassen, deren Namen mit `test` beginnen. - ---- - -## 2. Wichtigste Features von `pytest` (mit Beispielen) - -### 2.1 Einfache Test-Schreibweise (statt `unittest.TestCase`) - -**unittest-Version:** - -```python -import unittest - -class TestMath(unittest.TestCase): - def test_addition(self): - self.assertEqual(1 + 1, 2) - -if __name__ == '__main__': - unittest.main() -``` - -**pytest-Version:** - -```python -def test_addition(): - assert 1 + 1 == 2 -``` - -Unterschiede: - -- Kein TestCase-Subclass, keine `self.assert*`-Methoden nötig. -- normale `assert`-Statements, die von `pytest` „umgeschrieben“ werden, um bei Fehlern sehr gute Fehlermeldungen zu erzeugen. - ---- - -### 2.2 Intelligente Fehlermeldungen durch Assert-Rewriting - -```python -def test_list_contains(): - items = [1, 2, 3] - assert 4 in items -``` - -Fehlerausgabe (vereinfacht): - -```text -> assert 4 in items -E assert 4 in [1, 2, 3] -``` - -Bei komplexeren Ausdrücken zeigt `pytest` oft die einzelnen Teilausdrücke und deren Werte. - ---- - -### 2.3 Fixtures: Wiederverwendbare Setup-/Teardown-Logik - -Fixtures sind einer der größten Vorteile von `pytest` im Vergleich zu `unittest.setUp/tearDown`. - -**Beispiel: einfache Fixture** - -```python -import pytest - -@pytest.fixture -def sample_user(): - return {"id": 1, "name": "Alice"} - -def test_user_has_name(sample_user): - assert sample_user["name"] == "Alice" -``` - -- Fixture `sample_user` wird automatisch per Parameter in den Test injiziert. -- Kein explizites Aufrufen, kein Erben von Klassen notwendig. - -**Mit Setup & Teardown (yield-Fixture)** - -```python -import pytest -import sqlite3 - -@pytest.fixture -def db_connection(): - conn = sqlite3.connect(":memory:") - # Setup: Tabelle anlegen - conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)") - yield conn - # Teardown: Verbindung schließen - conn.close() - -def test_insert_user(db_connection): - db_connection.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)) - cursor = db_connection.execute("SELECT COUNT(*) FROM users") - count = cursor.fetchone()[0] - assert count == 1 -``` - ---- - -### 2.4 Fixture-Scopes: Performance optimieren - -Scope-Optionen: `function`, `class`, `module`, `package`, `session`. - -```python -import pytest - -@pytest.fixture(scope="session") -def expensive_resource(): - print("Init expensive resource once per session") - return {"data": "big thing"} - -def test_one(expensive_resource): - assert expensive_resource["data"] == "big thing" - -def test_two(expensive_resource): - assert "data" in expensive_resource -``` - -Die Fixture wird nur einmal pro Testlauf erstellt, nicht pro Testfunktion. - ---- - -### 2.5 Parametrisierung: Viele Testfälle mit wenig Code - -```python -import pytest - -@pytest.mark.parametrize( - "a, b, expected", - [ - (1, 2, 3), - (0, 0, 0), - (-1, 1, 0), - ], -) -def test_add(a, b, expected): - assert a + b == expected -``` - -`pytest` erzeugt automatisch drei Testfälle: - -- `test_add[1-2-3]` -- `test_add[0-0-0]` -- `test_add[-1-1-0]` - -Im Vergleich dazu ist Parametrisierung in `unittest` umständlicher (z. B. mit `subTest` oder Metaklassen). - ---- - -### 2.6 Marker: Tests kategorisieren, skippen, xfail - -**skip/xfail** - -```python -import pytest -import sys - -@pytest.mark.skipif(sys.platform == "win32", reason="Does not run on Windows") -def test_only_on_unix(): - assert True - -@pytest.mark.xfail(reason="Bug #123 not fixed yet") -def test_known_bug(): - assert 1 / 0 == 1 # absichtlich falsch -``` - -**eigene Marker (z. B. „slow“, „integration“)** -`pytest.ini`: - -```ini -[pytest] -markers = - slow: marks tests as slow (deselect with '-m "not slow"') -``` - -Verwendung: - -```python -import pytest - -@pytest.mark.slow -def test_long_running(): - ... -``` - -Ausführen nur schneller Tests: - -```bash -pytest -m "not slow" -``` - ---- - -### 2.7 Testauswahl & Filter - -- `pytest path/to/tests` – Ordner/Datei wählen -- `pytest -k "name_part"` – filtern nach Testnamen - -```bash -pytest -k "addition" -``` - -führt nur Tests aus, deren Name „addition“ enthält. - ---- - -### 2.8 Plugins & Ecosystem - -Beispiele: - -- `pytest-cov` – Coverage-Reports -- `pytest-xdist` – parallele Testausführung -- `pytest-django`, `pytest-flask` – Framework-spezifische Fixtures -- `pytest-mock` – einfachere Nutzung von `unittest.mock` -- `pytest-rerunfailures` – flaky Tests neu starten - -Integration (z. B. Coverage): - -```bash -pip install pytest-cov -pytest --cov=mein_package --cov-report=term-missing -``` - ---- - -### 2.9 Integration mit `unittest` - -`pytest` findet und führt auch `unittest.TestCase`-Klassen aus: - -```python -import unittest - -class TestMath(unittest.TestCase): - def test_addition(self): - self.assertEqual(1 + 1, 2) -``` - -Einfach mit `pytest` laufen lassen – keine Änderung nötig. Das erleichtert die schrittweise Migration. - ---- - -## 3. Typische Use-Cases für `pytest` - -### 3.1 Unit-Tests (Funktionen, Klassen, Methoden) - -```python -def is_even(n: int) -> bool: - return n % 2 == 0 - -def test_is_even(): - assert is_even(2) - assert not is_even(3) -``` - ---- - -### 3.2 Integrationstests (z. B. Datenbank + Service) - -```python -@pytest.fixture -def user_service(db_connection): - # Imagine a service that uses the DB connection - class UserService: - def create_user(self, name): - db_connection.execute("INSERT INTO users (name) VALUES (?)", (name,)) - def count_users(self): - return db_connection.execute("SELECT COUNT(*) FROM users").fetchone()[0] - return UserService() - -def test_user_service_integration(user_service): - user_service.create_user("Alice") - user_service.create_user("Bob") - assert user_service.count_users() == 2 -``` - ---- - -### 3.3 API-Tests (REST, HTTP) - -```python -import requests - -@pytest.mark.integration -def test_public_api(): - resp = requests.get("https://httpbin.org/get") - assert resp.status_code == 200 - data = resp.json() - assert "url" in data -``` - ---- - -### 3.4 CLI-Tests - -```python -import subprocess - -def test_cli_help(): - result = subprocess.run(["mytool", "--help"], capture_output=True, text=True) - assert result.returncode == 0 - assert "Usage:" in result.stdout -``` - ---- - -### 3.5 Property-based Testing (mit Hypothesis) - -```bash -pip install hypothesis pytest -``` - -```python -from hypothesis import given, strategies as st - -def my_add(a, b): - return a + b - -@given(st.integers(), st.integers()) -def test_my_add_commutative(a, b): - assert my_add(a, b) == my_add(b, a) -``` - ---- - -## 4. Was gehört zu einem kompletten & effizienten Testframework / Test-Suite? - -### 4.1 Struktur des Projekts - -Typische Struktur: - -```text -myproject/ - mypackage/ - __init__.py - core.py - ... - tests/ - __init__.py (optional) - conftest.py - test_core.py - test_api.py - integration/ - test_db_integration.py -``` - -- Tests in separatem `tests/`-Ordner -- `conftest.py` für gemeinsame Fixtures/Konfiguration - ---- - -### 4.2 Gemeinsame Fixtures in `conftest.py` - -`tests/conftest.py`: - -```python -import pytest -from mypackage.app import create_app - -@pytest.fixture(scope="session") -def app(): - return create_app(testing=True) - -@pytest.fixture -def client(app): - return app.test_client() # Beispiel: Flask -``` - -Tests können `client` direkt verwenden: - -```python -def test_health_check(client): - resp = client.get("/health") - assert resp.status_code == 200 -``` - ---- - -### 4.3 Konfigurationsdatei `pytest.ini` / `pyproject.toml` - -`pytest.ini`: - -```ini -[pytest] -addopts = -ra -q -testpaths = tests -markers = - slow: slow tests - integration: integration tests -``` - -Damit: - -- Standardoptionen (`-ra`: Zusammenfassung von Skips/Xfails, `-q`: quiet) -- Testpfade -- Marker-Definitionen, damit keine Warnungen kommen - ---- - -### 4.4 Coverage-Integration - -```bash -pip install pytest-cov -``` - -`pytest.ini`: - -```ini -[pytest] -addopts = --cov=mypackage --cov-report=term-missing -``` - -Ausführen: - -```bash -pytest -``` - -Coverage ist immer aktiviert. - ---- - -### 4.5 Testtypen trennen (schnell vs. langsam, Unit vs. Integration) - -Beispiel mit Markern: - -```python -import pytest - -@pytest.mark.slow -def test_big_data_processing(): - ... - -@pytest.mark.integration -def test_external_service(): - ... -``` - -- In CI: Unit-Tests ohne `slow`/`integration` häufig laufen lassen -- Nacht-Build: alle Tests - -```bash -pytest -m "not slow and not integration" -``` - ---- - -### 4.6 CI/CD-Integration (GitHub Actions Beispiel) - -`.github/workflows/tests.yml`: - -```yaml -name: Tests - -on: [push, pull_request] - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - run: pip install -r requirements.txt - - run: pytest -``` - ---- - -### 4.7 Testdaten-Strategie - -- Factory-Funktionen/Fixtures statt harte Testdaten -- z. B. `factory_boy` oder einfache Fixtures: - -```python -import itertools -import pytest - -_id_counter = itertools.count(1) - -@pytest.fixture -def user_factory(): - def _create_user(name="User", active=True): - return { - "id": next(_id_counter), - "name": name, - "active": active, - } - return _create_user - -def test_create_multiple_users(user_factory): - u1 = user_factory(name="Alice") - u2 = user_factory(name="Bob", active=False) - assert u1["id"] != u2["id"] -``` - ---- - -### 4.8 Mocking / Patching - -Mit `unittest.mock` direkt oder über `pytest-mock`: - -```bash -pip install pytest-mock -``` - -```python -def send_email(address, subject): - # send email ... - ... - -def notify_user(user): - send_email(user.email, "Welcome!") - -def test_notify_user(mocker): - mock_send = mocker.patch(__name__ + ".send_email") - class User: - email = "test@example.com" - notify_user(User()) - mock_send.assert_called_once_with("test@example.com", "Welcome!") -``` - ---- - -## 5. Herausforderungen mit `pytest` (und wie man damit umgeht) - -### 5.1 „Magie“ und versteckte Abhängigkeiten bei Fixtures - -Problem: - -- Viele Fixtures in `conftest.py` -- Testfunktionen haben lange Parameterlisten -- Schwer nachzuvollziehen, woher Werte kommen - -Ansätze: - -- Fixtures möglichst lokal definieren, wenn sie nur in wenigen Tests gebraucht werden -- Namespacing durch mehrere `conftest.py` in Unterordnern -- Gute Benennung + Docstrings der Fixtures - ---- - -### 5.2 Komplexe Fixture-Hierarchien - -Z. B. Fixture A hängt von B, B von C, etc. – schwer zu verstehen. - -Empfehlungen: - -- Komplexe Abhängigkeiten dokumentieren -- ggf. in Helper-Funktionen auslagern, statt in Fixture-Graph zu pressen -- fixere Struktur pro Layer (Unit vs. Integration) trennen - ---- - -### 5.3 Performance - -Typische Ursachen: - -- Fixtures mit teurem Setup ohne größeren Scope (`function` statt `session`) -- Datenbankzugriffe in vielen Tests -- Externe Dienste (Netzwerk) - -Lösungen: - -- Fixture-Scopes gezielt einsetzen (`module`, `session`) -- Datenbank pro Testlauf statt pro Test zurücksetzen (z. B. via Transaktionen) -- Externe Dienste mocken -- Parallele Ausführung mit `pytest-xdist`: - -```bash -pip install pytest-xdist -pytest -n auto -``` - ---- - -### 5.4 Flaky Tests - -Tests, die manchmal grün, manchmal rot sind (Zeit, Zufall, externe Abhängigkeiten). - -Maßnahmen: - -- Zeitabhängigkeiten mocken (`freezegun`, eigene Time-Adapter) -- Zufall mit deterministischem Seed versehen -- Externe Dienste mocken oder stubben -- Falls gar nicht vermeidbar: Plugins wie `pytest-rerunfailures` nutzen (aber das ist eher die letzte Option) - ---- - -### 5.5 Migration von `unittest` zu `pytest` - -Herausforderungen: - -- Gewohnheit (xUnit-Stil vs. funktionaler Stil) -- Mischung aus alten `unittest`-Tests und neuen `pytest`-Tests - -Strategie: - -1. Bestehende `unittest`-Tests unverändert lassen; nur `pytest` statt `python -m unittest` verwenden. -2. Neue Tests im `pytest`-Stil schreiben. -3. Schrittweise Klassen in einfache Testfunktionen migrieren, wo sinnvoll. -4. Gemeinsame Setups aus `setUp/tearDown` in Fixtures extrahieren. - ---- - -## 6. Vergleich `pytest` vs. `unittest` im Detail - -### 6.1 Syntax und Stil - -**unittest:** - -```python -import unittest - -class TestCalculator(unittest.TestCase): - def setUp(self): - self.calc = Calculator() - - def test_add(self): - self.assertEqual(self.calc.add(1, 2), 3) - - def test_subtract(self): - self.assertEqual(self.calc.subtract(5, 3), 2) -``` - -**pytest:** - -```python -import pytest - -@pytest.fixture -def calc(): - return Calculator() - -def test_add(calc): - assert calc.add(1, 2) == 3 - -def test_subtract(calc): - assert calc.subtract(5, 3) == 2 -``` - -Gemeinsamkeiten: - -- Beide Frameworks unterstützen Setup/Teardown, Assertions, Testdiscovery. - -Unterschiede: - -- `unittest`: OOP/xUnit-Stil, Methoden in Klassen, `self.assert*`. -- `pytest`: funktionaler Stil, freie Funktionen, normale `assert`-Statements, Fixtures statt `setUp/tearDown`. - ---- - -### 6.2 Assertions - -**unittest:** - -```python -self.assertEqual(a, b) -self.assertTrue(cond) -self.assertRaises(ValueError, func, arg1) -``` - -**pytest:** - -```python -assert a == b -assert cond -import pytest -with pytest.raises(ValueError): - func(arg1) -``` - -Vorteile bei `pytest`: - -- Weniger Boilerplate. -- Ausführlichere Fehlermeldungen bei komplexen Ausdrücken. - ---- - -### 6.3 Setup/Teardown vs. Fixtures - -**unittest:** - -```python -class TestSomething(unittest.TestCase): - def setUp(self): - self.db = setup_db() - - def tearDown(self): - self.db.close() -``` - -**pytest mit Fixture:** - -```python -@pytest.fixture -def db(): - connection = setup_db() - yield connection - connection.close() -``` - -Die Fixture kann von beliebig vielen Tests wiederverwendet werden – auch über verschiedene Dateien hinweg (via `conftest.py`). - ---- - -### 6.4 Parametrisierung - -**unittest mit `subTest`:** - -```python -class TestAdd(unittest.TestCase): - def test_add(self): - cases = [(1, 2, 3), (0, 0, 0), (-1, 1, 0)] - for a, b, expected in cases: - with self.subTest(a=a, b=b): - self.assertEqual(a + b, expected) -``` - -**pytest:** - -```python -@pytest.mark.parametrize("a, b, expected", [ - (1, 2, 3), - (0, 0, 0), - (-1, 1, 0), -]) -def test_add(a, b, expected): - assert a + b == expected -``` - -`pytest` erzeugt separate Testfälle, was Reporting und Debugging erleichtert. - ---- - -### 6.5 Plugins vs. Standardbibliothek - -- `unittest` ist Teil der Standardbibliothek – keine zusätzliche Abhängigkeit. -- `pytest` ist ein Drittanbieterpaket, aber: - - deutlich mehr Plugins und Erweiterungen, - - in der Praxis weit verbreitet (De-Facto-Standard in vielen Python-Projekten). - ---- - -### 6.6 Interoperabilität - -- `pytest` kann `unittest`-Tests ausführen. -- `unittest` weiß nichts von `pytest`-Tests. - -Dadurch eignet sich `pytest` gut für eine schrittweise Migration. - ---- - -## 7. Fazit & Empfehlung fürs Vorgehen - -Wenn du bisher mit `unittest` gearbeitet hast, ist eine typische Übergangsstrategie: - -1. **Start einfach:** Installiere `pytest`, führe deine bestehenden `unittest`-Tests mit `pytest` aus. -2. **Neue Tests mit pytest-Stil:** Für neue Features nur noch `pytest`-Tests (Funktionen, Fixtures, Parametrisierung). -3. **SetUp/Teardown migrieren:** Häufige Setups in `unittest` schrittweise in `pytest`-Fixtures nach `conftest.py` überführen. -4. **Plugins nutzen:** - - `pytest-cov` für Coverage, - - optional `pytest-xdist` für Speed, - - `pytest-mock` für Mocking. -1. **Marker & Struktur etablieren:** Tests in schnelle/ langsame, Unit/Integration gliedern; sinnvolle Projektstruktur und `pytest.ini` anlegen. -# Pytest Official: Get Started -[Get Started — pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) - -# Real Python: Effective Python Testing With pytest -[Effective Python Testing With pytest – Real Python](https://realpython.com/pytest-python-testing/) - -`pytest` is a popular testing framework for Python that simplifies the process of writing and executing tests. To start using `pytest`, install it with `pip` in a virtual environment. `pytest` offers several advantages over `unittest` that ships with Python, such as less boilerplate code, more readable output, and a rich plugin ecosystem. - -**By the end of this tutorial, you’ll understand that:** - -- Using `pytest` requires **installing it with `pip`** in a virtual environment to set up the `pytest` command. -- `pytest` allows for less code, easier readability, and more features **compared to `unittest`**. -- Managing **test dependencies** and **state** with `pytest` is made efficient through the use of **fixtures**, which provide explicit dependency declarations. -- **Parametrization** in `pytest` helps avoid redundant test code by allowing multiple test scenarios from a single test function. -- **Assertion introspection** in `pytest` provides detailed information about failures in the test report. - -**Free Bonus:** [5 Thoughts On Python Mastery](https://realpython.com/bonus/python-mastery-course/), a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level. - - ==**Take the Quiz:**== Test your knowledge with our interactive “Effective Testing with Pytest” quiz. You’ll receive a score upon completion to help you track your learning progress: - ---- - -[ - -![Effective Python Testing With Pytest](https://files.realpython.com/media/Intermediate-Advanced-PyTest-Features_Watermarked.43fb169e7121.jpg) - - - -](https://realpython.com/quizzes/effective-testing-with-pytest/) - -**Interactive Quiz** - -[Effective Testing with Pytest](https://realpython.com/quizzes/effective-testing-with-pytest/) - -In this quiz, you'll test your understanding of pytest, a Python testing tool. With this knowledge, you'll be able to write more efficient and effective tests, ensuring your code behaves as expected. - -## How to Install `pytest`[](https://realpython.com/pytest-python-testing/#how-to-install-pytest "Permanent link") - -To follow along with some of the examples in this tutorial, you’ll need to install `pytest`. As most [Python packages](https://realpython.com/python-modules-packages/), `pytest` is available on [PyPI](https://realpython.com/pypi-publish-python-package/). You can install it in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/) using [`pip`](https://realpython.com/what-is-pip/): - -- [Windows](https://realpython.com/pytest-python-testing/#windows-1) -- [Linux + macOS](https://realpython.com/pytest-python-testing/#linux-macos-1) - -`PS> python -m venv venv PS> .\venv\Scripts\activate (venv) PS> python -m pip install pytest` - -The `pytest` command will now be available in your installation environment. - -[Remove ads](https://realpython.com/account/join/) - -## What Makes `pytest` So Useful?[](https://realpython.com/pytest-python-testing/#what-makes-pytest-so-useful "Permanent link") - -If you’ve written unit [tests](https://realpython.com/python-testing/) for your Python code before, then you may have used Python’s built-in **`unittest`** module. `unittest` provides a solid base on which to build your test suite, but it has a few shortcomings. - -A number of third-party testing frameworks attempt to address some of the issues with `unittest`, and `pytest` has proven to be one of the most popular. `pytest` is a feature-rich, plugin-based ecosystem for testing your Python code. - -If you haven’t had the pleasure of using `pytest` yet, then you’re in for a treat! Its philosophy and features will make your testing experience more productive and enjoyable. With `pytest`, common tasks require less code and advanced tasks can be achieved through a variety of time-saving commands and plugins. It’ll even run your existing tests out of the box, including those written with `unittest`. - -As with most frameworks, some development patterns that make sense when you first start using `pytest` can start causing pains as your test suite grows. This tutorial will help you understand some of the tools `pytest` provides to keep your testing efficient and effective even as it scales. - -### Less Boilerplate[](https://realpython.com/pytest-python-testing/#less-boilerplate "Permanent link") - -Most functional tests follow the Arrange-Act-Assert model: - -1. **Arrange**, or set up, the conditions for the test -2. **Act** by calling some function or method -3. **Assert** that some end condition is true - -Testing frameworks typically hook into your test’s [assertions](https://realpython.com/python-assert-statement/) so that they can provide information when an assertion fails. `unittest`, for example, provides a number of helpful assertion utilities out of the box. However, even a small set of tests requires a fair amount of [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code). - -Imagine you’d like to write a test suite just to make sure that `unittest` is working properly in your project. You might want to write one test that always passes and one that always fails: - -`test_with_unittest.py` - -`from unittest import TestCase class TryTesting(TestCase): def test_always_passes(self): self.assertTrue(True) def test_always_fails(self): self.assertTrue(False)` - -You can then run those tests from the command line using the `discover` option of `unittest`: - -`(venv) $ python -m unittest discover F. ====================================================================== FAIL: test_always_fails (test_with_unittest.TryTesting) ---------------------------------------------------------------------- Traceback (most recent call last): File "...\effective-python-testing-with-pytest\test_with_unittest.py", line 10, in test_always_fails self.assertTrue(False) AssertionError: False is not true ---------------------------------------------------------------------- Ran 2 tests in 0.006s FAILED (failures=1)` - -As expected, one test passed and one failed. You’ve proven that `unittest` is working, but look at what you had to do: - -1. Import the `TestCase` class from `unittest` -2. Create `TryTesting`, a [subclass](https://realpython.com/python3-object-oriented-programming/) of `TestCase` -3. Write a method in `TryTesting` for each test -4. Use one of the `self.assert*` methods from `unittest.TestCase` to make assertions - -That’s a significant amount of code to write, and because it’s the minimum you need for _any_ test, you’d end up writing the same code over and over. `pytest` simplifies this workflow by allowing you to use normal functions and Python’s `assert` keyword directly: - -`test_with_pytest.py` - -`def test_always_passes(): assert True def test_always_fails(): assert False` - -That’s it. You don’t have to deal with any imports or classes. All you need to do is include a function with the `test_` prefix. Because you can use the `assert` keyword, you don’t need to learn or remember all the different `self.assert*` methods in `unittest`, either. If you can write an expression that you expect to evaluate to `True`, and then `pytest` will test it for you. - -Not only does `pytest` eliminate a lot of boilerplate, but it also provides you with a much more detailed and easy-to-read output. - -### Nicer Output[](https://realpython.com/pytest-python-testing/#nicer-output "Permanent link") - -You can run your test suite using the `pytest` command from the top-level folder of your project: - -`(venv) $ pytest ============================= test session starts ============================= platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0 rootdir: ...\effective-python-testing-with-pytest collected 4 items test_with_pytest.py .F [ 50%] test_with_unittest.py F. [100%] ================================== FAILURES =================================== ______________________________ test_always_fails ______________________________ def test_always_fails(): > assert False E assert False test_with_pytest.py:7: AssertionError ________________________ TryTesting.test_always_fails _________________________ self = def test_always_fails(self): > self.assertTrue(False) E AssertionError: False is not true test_with_unittest.py:10: AssertionError =========================== short test summary info =========================== FAILED test_with_pytest.py::test_always_fails - assert False FAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:... ========================= 2 failed, 2 passed in 0.20s =========================` - -`pytest` presents the test results differently than `unittest`, and the `test_with_unittest.py` file was also automatically included. The report shows: - -1. The system state, including which versions of Python, `pytest`, and any plugins you have installed -2. The `rootdir`, or the directory to search under for configuration and tests -3. The number of tests the runner discovered - -These items are presented in the first section of the output: - -`============================= test session starts ============================= platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0 rootdir: ...\effective-python-testing-with-pytest collected 4 items` - -The output then indicates the status of each test using a syntax similar to `unittest`: - -- **A dot (`.`)** means that the test passed. -- **An `F`** means that the test has failed. -- **An `E`** means that the test raised an unexpected exception. - -The special characters are shown next to the name with the overall progress of the test suite shown on the right: - -`test_with_pytest.py .F [ 50%] test_with_unittest.py F. [100%]` - -For tests that fail, the report gives a detailed breakdown of the failure. In the example, the tests failed because `assert False` always fails: - -`================================== FAILURES =================================== ______________________________ test_always_fails ______________________________ def test_always_fails(): > assert False E assert False test_with_pytest.py:7: AssertionError ________________________ TryTesting.test_always_fails _________________________ self = def test_always_fails(self): > self.assertTrue(False) E AssertionError: False is not true test_with_unittest.py:10: AssertionError` - -This extra output can come in extremely handy when debugging. Finally, the report gives an overall status report of the test suite: - -`=========================== short test summary info =========================== FAILED test_with_pytest.py::test_always_fails - assert False FAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:... ========================= 2 failed, 2 passed in 0.20s =========================` - -When compared to unittest, the `pytest` output is much more informative and readable. - -In the next section, you’ll take a closer look at how `pytest` takes advantage of the existing `assert` keyword. - -[Remove ads](https://realpython.com/account/join/) - -### Less to Learn[](https://realpython.com/pytest-python-testing/#less-to-learn "Permanent link") - -Being able to use the [`assert`](https://realpython.com/python-assert-statement/) keyword is also powerful. If you’ve used it before, then there’s nothing new to learn. Here are a few assertion examples so you can get an idea of the types of test you can make: - -`test_assert_examples.py` - -`def test_uppercase(): assert "loud noises".upper() == "LOUD NOISES" def test_reversed(): assert list(reversed([1, 2, 3, 4])) == [4, 3, 2, 1] def test_some_primes(): assert 37 in { num for num in range(2, 50) if not any(num % div == 0 for div in range(2, num)) }` - -They look very much like normal Python functions. All of this makes the learning curve for `pytest` shallower than it is for `unittest` because you don’t need to learn new constructs to get started. - -Note that each test is quite small and self-contained. This is common—you’ll see long function names and not a lot going on within a function. This serves mainly to keep your tests isolated from each other, so if something breaks, you know exactly where the problem is. A nice side effect is that the labeling is much better in the output. - -To see an example of a project that creates a test suite along with the main project, check out the [Build a Hash Table in Python With TDD](https://realpython.com/python-hash-table/) tutorial. Additionally, you can work on Python practice problems to try test-driven development yourself while you [get ready for your next interview](https://realpython.com/python-practice-problems/) or [parse CSV files](https://realpython.com/python-interview-problem-parsing-csv-files/). - -In the next section, you’re going to be examining fixtures, a great pytest feature to help you manage test input values. - -### Easier to Manage State and Dependencies[](https://realpython.com/pytest-python-testing/#easier-to-manage-state-and-dependencies "Permanent link") - -Your tests will often depend on types of data or [test doubles](https://en.wikipedia.org/wiki/Test_double) that mock objects your code is likely to encounter, such as [dictionaries](https://realpython.com/python-dicts/) or [JSON](https://realpython.com/python-json/) files. - -With `unittest`, you might extract these dependencies into `.setUp()` and `.tearDown()` methods so that each test in the class can make use of them. Using these special methods is fine, but as your test classes get larger, you may inadvertently make the test’s dependence entirely **implicit**. In other words, by looking at one of the many tests in isolation, you may not immediately see that it depends on something else. - -Over time, implicit dependencies can lead to a complex tangle of code that you have to unwind to make sense of your tests. Tests should help to make your code more understandable. If the tests themselves are difficult to understand, then you may be in trouble! - -`pytest` takes a different approach. It leads you toward **explicit** dependency declarations that are still reusable thanks to the availability of [fixtures](https://docs.pytest.org/en/latest/fixture.html). `pytest` fixtures are functions that can create data, test doubles, or initialize system state for the test suite. Any test that wants to use a fixture must explicitly use this fixture function as an argument to the test function, so dependencies are always stated up front: - -`fixture_demo.py` - -`import pytest @pytest.fixture def example_fixture(): return 1 def test_with_fixture(example_fixture): assert example_fixture == 1` - -Looking at the test function, you can immediately tell that it depends on a fixture, without needing to check the whole file for fixture definitions. - -**Note:** You usually want to put your tests into their own folder called `tests` at the root level of your project. - -For more information about structuring a Python application, check out the [video course](https://realpython.com/courses/structuring-python-application/) on that very topic. - -Fixtures can also make use of other fixtures, again by declaring them explicitly as dependencies. That means that, over time, your fixtures can become bulky and modular. Although the ability to insert fixtures into other fixtures provides enormous flexibility, it can also make managing dependencies more challenging as your test suite grows. - -Later in this tutorial, you’ll learn [more about fixtures](https://realpython.com/pytest-python-testing/#fixtures-managing-state-and-dependencies) and try a few techniques for handling these challenges. - -### Easy to Filter Tests[](https://realpython.com/pytest-python-testing/#easy-to-filter-tests "Permanent link") - -As your test suite grows, you may find that you want to run just a few tests on a feature and save the full suite for later. `pytest` provides a few ways of doing this: - -- **Name-based filtering**: You can limit `pytest` to running only those tests whose fully qualified names match a particular expression. You can do this with the `-k` parameter. -- **Directory scoping**: By default, `pytest` will run only those tests that are in or under the current directory. -- **Test categorization**: `pytest` can include or exclude tests from particular categories that you define. You can do this with the `-m` parameter. - -Test categorization in particular is a subtly powerful tool. `pytest` enables you to create **marks**, or custom labels, for any test you like. A test may have multiple labels, and you can use them for granular control over which tests to run. Later in this tutorial, you’ll see an example of [how `pytest` marks work](https://realpython.com/pytest-python-testing/#marks-categorizing-tests) and learn how to make use of them in a large test suite. - -[Remove ads](https://realpython.com/account/join/) - -### Allows Test Parametrization[](https://realpython.com/pytest-python-testing/#allows-test-parametrization "Permanent link") - -When you’re testing functions that process data or perform generic transformations, you’ll find yourself writing many similar tests. They may differ only in the [input or output](https://realpython.com/python-input-output/) of the code being tested. This requires duplicating test code, and doing so can sometimes obscure the behavior that you’re trying to test. - -`unittest` offers a way of collecting several tests into one, but they don’t show up as individual tests in result reports. If one test fails and the rest pass, then the entire group will still return a single failing result. `pytest` offers its own solution in which each test can pass or fail independently. You’ll see [how to parametrize tests](https://realpython.com/pytest-python-testing/#parametrization-combining-tests) with `pytest` later in this tutorial. - -### Has a Plugin-Based Architecture[](https://realpython.com/pytest-python-testing/#has-a-plugin-based-architecture "Permanent link") - -One of the most beautiful features of `pytest` is its openness to customization and new features. Almost every piece of the program can be cracked open and changed. As a result, `pytest` users have developed a rich ecosystem of helpful plugins. - -Although some `pytest` plugins focus on specific frameworks like [Django](https://www.djangoproject.com/), others are applicable to most test suites. You’ll see [details on some specific plugins](https://realpython.com/pytest-python-testing/#useful-pytest-plugins) later in this tutorial. - -## Fixtures: Managing State and Dependencies[](https://realpython.com/pytest-python-testing/#fixtures-managing-state-and-dependencies "Permanent link") - -`pytest` fixtures are a way of providing data, test doubles, or state setup to your tests. Fixtures are functions that can return a wide range of values. Each test that depends on a fixture must explicitly accept that fixture as an argument. - -### When to Create Fixtures[](https://realpython.com/pytest-python-testing/#when-to-create-fixtures "Permanent link") - -In this section, you’ll simulate a typical [test-driven development](https://realpython.com/courses/test-driven-development-pytest/) (TDD) workflow. - -Imagine you’re writing a function, `format_data_for_display()`, to process the data returned by an API endpoint. The data represents a list of people, each with a given name, family name, and job title. The function should output a list of strings that include each person’s full name (their `given_name` followed by their `family_name`), a colon, and their `title`: - -`format_data.py` - -`def format_data_for_display(people): ... # Implement this!` - -In good TDD fashion, you’ll want to first write a test for it. You might write the following code for that: - -`test_format_data.py` - -`def test_format_data_for_display(): people = [ { "given_name": "Alfonsa", "family_name": "Ruiz", "title": "Senior Software Engineer", }, { "given_name": "Sayid", "family_name": "Khan", "title": "Project Manager", }, ] assert format_data_for_display(people) == [ "Alfonsa Ruiz: Senior Software Engineer", "Sayid Khan: Project Manager", ]` - -While writing this test, it occurs to you that you may need to write another function to transform the data into comma-separated values for use in [Excel](https://realpython.com/openpyxl-excel-spreadsheets-python/): - -`format_data.py` - -`def format_data_for_display(people): ... # Implement this! def format_data_for_excel(people): ... # Implement this!` - -Your to-do list grows! That’s good! One of the advantages of TDD is that it helps you plan out the work ahead. The test for the `format_data_for_excel()` function would look awfully similar to the `format_data_for_display()` function: - -`test_format_data.py` - -`def test_format_data_for_display(): # ... def test_format_data_for_excel(): people = [ { "given_name": "Alfonsa", "family_name": "Ruiz", "title": "Senior Software Engineer", }, { "given_name": "Sayid", "family_name": "Khan", "title": "Project Manager", }, ] assert format_data_for_excel(people) == """given,family,title Alfonsa,Ruiz,Senior Software Engineer Sayid,Khan,Project Manager """` - -Notably, both the tests have to repeat the definition of the `people` variable, which is quite a few lines of code. - -If you find yourself writing several tests that all make use of the same underlying test data, then a fixture may be in your future. You can pull the repeated data into a single function decorated with `@pytest.fixture` to indicate that the function is a `pytest` fixture: - -`test_format_data.py` - -`import pytest @pytest.fixture def example_people_data(): return [ { "given_name": "Alfonsa", "family_name": "Ruiz", "title": "Senior Software Engineer", }, { "given_name": "Sayid", "family_name": "Khan", "title": "Project Manager", }, ] # ...` - -You can use the fixture by adding the function reference as an argument to your tests. Note that you don’t call the fixture function. `pytest` takes care of that. You’ll be able to use the return value of the fixture function as the name of the fixture function: - -`test_format_data.py` - -`# ... def test_format_data_for_display(example_people_data): assert format_data_for_display(example_people_data) == [ "Alfonsa Ruiz: Senior Software Engineer", "Sayid Khan: Project Manager", ] def test_format_data_for_excel(example_people_data): assert format_data_for_excel(example_people_data) == """given,family,title Alfonsa,Ruiz,Senior Software Engineer Sayid,Khan,Project Manager """` - -Each test is now notably shorter but still has a clear path back to the data it depends on. Be sure to name your fixture something specific. That way, you can quickly determine if you want to use it when writing new tests in the future! - -When you first discover the power of fixtures, it can be tempting to use them all the time, but as with all things, there’s a balance to be maintained. - -[Remove ads](https://realpython.com/account/join/) - -### When to Avoid Fixtures[](https://realpython.com/pytest-python-testing/#when-to-avoid-fixtures "Permanent link") - -Fixtures are great for extracting data or objects that you use across multiple tests. However, they aren’t always as good for tests that require slight variations in the data. Littering your test suite with fixtures is no better than littering it with plain data or objects. It might even be worse because of the added layer of indirection. - -As with most abstractions, it takes some practice and thought to find the right level of fixture use. - -Nevertheless, fixtures will likely be an integral part of your test suite. As your project grows in scope, the challenge of scale starts to come into the picture. One of the challenges facing any kind of tool is how it handles being used at scale, and luckily, `pytest` has a bunch of useful features that can help you manage the complexity that comes with growth. - -### How to Use Fixtures at Scale[](https://realpython.com/pytest-python-testing/#how-to-use-fixtures-at-scale "Permanent link") - -As you extract more fixtures from your tests, you might see that some fixtures could benefit from further abstraction. In `pytest`, fixtures are **modular**. Being modular means that fixtures can be [imported](https://realpython.com/python-import/), can import other modules, and they can depend on and import other fixtures. All this allows you to compose a suitable fixture abstraction for your use case. - -For example, you may find that fixtures in two separate files, or [modules](https://realpython.com/python-modules-packages/), share a common dependency. In this case, you can move fixtures from test modules into more general fixture-related modules. That way, you can import them back into any test modules that need them. This is a good approach when you find yourself using a fixture repeatedly throughout your project. - -If you want to make a fixture available for your whole project without having to import it, a special configuration module called [`conftest.py`](https://docs.pytest.org/en/6.2.x/fixture.html#conftest-py-sharing-fixtures-across-multiple-files) will allow you to do that. - -`pytest` looks for a `conftest.py` module in each directory. If you add your general-purpose fixtures to the `conftest.py` module, then you’ll be able to use that fixture throughout the module’s parent directory and in any subdirectories without having to import it. This is a great place to put your most widely used fixtures. - -Another interesting use case for fixtures and `conftest.py` is in guarding access to resources. Imagine that you’ve written a test suite for code that deals with [API calls](https://realpython.com/api-integration-in-python/). You want to ensure that the test suite doesn’t make any real network calls even if someone accidentally writes a test that does so. - -`pytest` provides a [`monkeypatch`](https://docs.pytest.org/en/latest/monkeypatch.html) fixture to replace values and behaviors, which you can use to great effect: - -`conftest.py` - -`import pytest import requests @pytest.fixture(autouse=True) def disable_network_calls(monkeypatch): def stunted_get(): raise RuntimeError("Network access not allowed during testing!") monkeypatch.setattr(requests, "get", lambda *args, **kwargs: stunted_get())` - -By placing `disable_network_calls()` in `conftest.py` and adding the `autouse=True` option, you ensure that network calls will be disabled in every test across the suite. Any test that executes code calling `requests.get()` will raise a `RuntimeError` indicating that an unexpected network call would have occurred. - -Your test suite is growing in numbers, which gives you a great feeling of confidence to make changes and not break things unexpectedly. That said, as your test suite grows, it might start taking a long time. Even if it doesn’t take that long, perhaps you’re focusing on some core behavior that trickles down and breaks most tests. In these cases, you might want to limit the test runner to only a certain category of tests. - -## Marks: Categorizing Tests[](https://realpython.com/pytest-python-testing/#marks-categorizing-tests "Permanent link") - -In any large test suite, it would be nice to avoid running _all_ the tests when you’re trying to iterate quickly on a new feature. Apart from the default behavior of `pytest` to run all tests in the current working directory, or the [filtering](https://docs.pytest.org/en/7.1.x/example/markers.html#using-k-expr-to-select-tests-based-on-their-name) functionality, you can take advantage of **markers**. - -`pytest` enables you to define categories for your tests and provides options for including or excluding categories when you run your suite. You can mark a test with any number of categories. - -Marking tests is useful for categorizing tests by subsystem or dependencies. If some of your tests require access to a database, for example, then you could create a `@pytest.mark.database_access` mark for them. - -**Pro tip**: Because you can give your marks any name you want, it can be easy to mistype or misremember the name of a mark. `pytest` will warn you about marks that it doesn’t recognize in the test output. - -You can use the `--strict-markers` flag to the `pytest` command to ensure that all marks in your tests are registered in your `pytest` configuration file, `pytest.ini`. It’ll prevent you from running your tests until you register any unknown marks. - -For more information on registering marks, check out the [`pytest` documentation](https://docs.pytest.org/en/latest/mark.html#registering-marks). - -When the time comes to run your tests, you can still run them all by default with the `pytest` command. If you’d like to run only those tests that require database access, then you can use `pytest -m database_access`. To run all tests _except_ those that require database access, you can use `pytest -m "not database_access"`. You can even use an `autouse` fixture to limit database access to those tests marked with `database_access`. - -Some plugins expand on the functionality of marks by adding their own guards. The [`pytest-django`](https://pytest-django.readthedocs.io/en/latest/) plugin, for instance, provides a `django_db` mark. Any tests without this mark that try to access the database will fail. The first test that tries to access the database will trigger the creation of Django’s test database. - -The requirement that you add the `django_db` mark nudges you toward stating your dependencies explicitly. That’s the `pytest` philosophy, after all! It also means that you can much more quickly run tests that don’t rely on the database, because `pytest -m "not django_db"` will prevent the test from triggering database creation. The time savings really add up, especially if you’re diligent about running your tests frequently. - -`pytest` provides a few marks out of the box: - -- **`skip`** skips a test unconditionally. -- **`skipif`** skips a test if the expression passed to it evaluates to `True`. -- **`xfail`** indicates that a test is expected to fail, so if the test _does_ fail, the overall suite can still result in a passing status. -- **`parametrize`** creates multiple variants of a test with different values as arguments. You’ll learn more about this mark shortly. - -You can see a list of all the marks that `pytest` knows about by running `pytest --markers`. - -On the topic of parametrization, that’s coming up next. - -[Remove ads](https://realpython.com/account/join/) - -## Parametrization: Combining Tests[](https://realpython.com/pytest-python-testing/#parametrization-combining-tests "Permanent link") - -You saw earlier in this tutorial how `pytest` fixtures can be used to reduce code duplication by extracting common dependencies. Fixtures aren’t quite as useful when you have several tests with slightly different inputs and expected outputs. In these cases, you can [**parametrize**](http://doc.pytest.org/en/latest/example/parametrize.html) a single test definition, and `pytest` will create variants of the test for you with the parameters you specify. - -Imagine you’ve written a function to tell if a string is a [palindrome](https://en.wikipedia.org/wiki/Palindrome). An initial set of tests could look like this: - -`def test_is_palindrome_empty_string(): assert is_palindrome("") def test_is_palindrome_single_character(): assert is_palindrome("a") def test_is_palindrome_mixed_casing(): assert is_palindrome("Bob") def test_is_palindrome_with_spaces(): assert is_palindrome("Never odd or even") def test_is_palindrome_with_punctuation(): assert is_palindrome("Do geese see God?") def test_is_palindrome_not_palindrome(): assert not is_palindrome("abc") def test_is_palindrome_not_quite(): assert not is_palindrome("abab")` - -All of these tests except the last two have the same shape: - -`def test_is_palindrome_(): assert is_palindrome("")` - -This is starting to smell a lot like boilerplate. `pytest` so far has helped you get rid of boilerplate, and it’s not about to let you down now. You can use `@pytest.mark.parametrize()` to fill in this shape with different values, reducing your test code significantly: - -`@pytest.mark.parametrize("palindrome", [ "", "a", "Bob", "Never odd or even", "Do geese see God?", ]) def test_is_palindrome(palindrome): assert is_palindrome(palindrome) @pytest.mark.parametrize("non_palindrome", [ "abc", "abab", ]) def test_is_palindrome_not_palindrome(non_palindrome): assert not is_palindrome(non_palindrome)` - -The first argument to `parametrize()` is a comma-delimited string of parameter names. You don’t have to provide more than one name, as you can see in this example. The second argument is a [list](https://realpython.com/python-list/) of either [tuples](https://realpython.com/python-tuple/) or single values that represent the parameter value(s). You could take your parametrization a step further to combine all your tests into one: - -`@pytest.mark.parametrize("maybe_palindrome, expected_result", [ ("", True), ("a", True), ("Bob", True), ("Never odd or even", True), ("Do geese see God?", True), ("abc", False), ("abab", False), ]) def test_is_palindrome(maybe_palindrome, expected_result): assert is_palindrome(maybe_palindrome) == expected_result` - -Even though this shortened your code, it’s important to note that in this case you actually lost some of the more descriptive nature of the original functions. Make sure you’re not parametrizing your test suite into incomprehensibility. You can use parametrization to separate the test data from the test behavior so that it’s clear what the test is testing, and also to make the different test cases easier to read and maintain. - -## Durations Reports: Fighting Slow Tests[](https://realpython.com/pytest-python-testing/#durations-reports-fighting-slow-tests "Permanent link") - -Each time you switch contexts from implementation code to test code, you incur some [overhead](https://en.wikipedia.org/wiki/Overhead_\(computing\)). If your tests are slow to begin with, then overhead can cause friction and frustration. - -You read earlier about using marks to filter out slow tests when you run your suite, but at some point you’re going to need to run them. If you want to improve the speed of your tests, then it’s useful to know _which_ tests might offer the biggest improvements. `pytest` can automatically record test durations for you and report the top offenders. - -Use the `--durations` option to the `pytest` command to include a duration report in your test results. `--durations` expects an integer value `n` and will report the slowest `n` number of tests. A new section will be included in your test report: - -`(venv) $ pytest --durations=5 ... ============================= slowest 5 durations ============================= 3.03s call test_code.py::test_request_read_timeout 1.07s call test_code.py::test_request_connection_timeout 0.57s call test_code.py::test_database_read (2 durations < 0.005s hidden. Use -vv to show these durations.) =========================== short test summary info =========================== ...` - -Each test that shows up in the durations report is a good candidate to speed up because it takes an above-average amount of the total testing time. Note that short durations are hidden by default. As spelled out in the report, you can increase the report verbosity and show these by passing `-vv` together with `--durations`. - -Be aware that some tests may have an invisible setup overhead. You read earlier about how the first test marked with `django_db` will trigger the creation of the Django test database. The `durations` report reflects the time it takes to set up the database in the test that triggered the database creation, which can be misleading. - -You’re well on your way to full test coverage. Next, you’ll be taking a look at some of the plugins that are part of the rich `pytest` plugin ecosystem. - -## Useful `pytest` Plugins[](https://realpython.com/pytest-python-testing/#useful-pytest-plugins "Permanent link") - -You learned about a few valuable `pytest` plugins earlier in this tutorial. In this section, you’ll be exploring those and a few others in more depth—everything from utility plugins like `pytest-randomly` to library-specific ones, like those for Django. - -[Remove ads](https://realpython.com/account/join/) - -### `pytest-randomly`[](https://realpython.com/pytest-python-testing/#pytest-randomly "Permanent link") - -Often the order of your tests is unimportant, but as your codebase grows, you may inadvertently introduce some side effects that could cause some tests to fail if they were run out of order. - -[`pytest-randomly`](https://github.com/pytest-dev/pytest-randomly) forces your tests to run in a random order. `pytest` always collects all the tests it can find before running them. `pytest-randomly` just shuffles that list of tests before execution. - -This is a great way to uncover tests that depend on running in a specific order, which means they have a **stateful dependency** on some other test. If you built your test suite from scratch in `pytest`, then this isn’t very likely. It’s more likely to happen in test suites that you migrate to `pytest`. - -The plugin will print a seed value in the configuration description. You can use that value to run the tests in the same order as you try to fix the issue. - -### `pytest-cov`[](https://realpython.com/pytest-python-testing/#pytest-cov "Permanent link") - -If you want to measure how well your tests cover your implementation code, then you can use the [coverage](https://coverage.readthedocs.io/) package. [`pytest-cov`](https://pytest-cov.readthedocs.io/en/latest/) integrates coverage, so you can run `pytest --cov` to see the test coverage report and boast about it on your project front page. - -### `pytest-django`[](https://realpython.com/pytest-python-testing/#pytest-django "Permanent link") - -[`pytest-django`](https://pytest-django.readthedocs.io/en/latest/) provides a handful of useful fixtures and marks for dealing with Django tests. You saw the `django_db` mark earlier in this tutorial. The `rf` fixture provides direct access to an instance of Django’s [`RequestFactory`](https://docs.djangoproject.com/en/3.0/topics/testing/advanced/#django.test.RequestFactory). The `settings` fixture provides a quick way to set or override Django settings. These plugins are a great boost to your Django testing productivity! - -If you’re interested in learning more about using `pytest` with Django, then check out [How to Provide Test Fixtures for Django Models in Pytest](https://realpython.com/django-pytest-fixtures/). - -### `pytest-bdd`[](https://realpython.com/pytest-python-testing/#pytest-bdd "Permanent link") - -`pytest` can be used to run tests that fall outside the traditional scope of unit testing. [Behavior-driven development](https://en.wikipedia.org/wiki/Behavior-driven_development) (BDD) encourages writing plain-language descriptions of likely user actions and expectations, which you can then use to determine whether to implement a given feature. [pytest-bdd](https://pytest-bdd.readthedocs.io/en/latest/) helps you use [Gherkin](http://docs.behat.org/en/v2.5/guides/1.gherkin.html) to write feature tests for your code. - -You can see which other plugins are available for `pytest` with this extensive [list of third-party plugins](https://docs.pytest.org/en/latest/reference/plugin_list.html). - -## Conclusion[](https://realpython.com/pytest-python-testing/#conclusion "Permanent link") - -`pytest` offers a core set of productivity features to filter and optimize your tests along with a flexible plugin system that extends its value even further. Whether you have a huge legacy `unittest` suite or you’re starting a new project from scratch, `pytest` has something to offer you. - -In this tutorial, you learned how to use: - -- **Fixtures** for handling test dependencies, state, and reusable functionality -- **Marks** for categorizing tests and limiting access to external resources -- **Parametrization** for reducing duplicated code between tests -- **Durations** to identify your slowest tests -- **Plugins** for integrating with other frameworks and testing tools - -Install `pytest` and give it a try. You’ll be glad you did. Happy testing! - -If you’re looking for an example project built with `pytest`, then check out the tutorial on [building a hash table with TDD](https://realpython.com/python-hash-table/), which will not only get you up to speed with `pytest`, but also help you master hash tables! - ---- -# Anhang: Prompt -```text -Kannst Du mir einen ausführlichen Überblick und ein Tutorial zu `pytest` geben. -- was sind die wichtigsten Features? -- Welche Use-Cases werden abgedeckt? -- Was gehört typischerweise zu einem kompletten und effizeinten Testframework oder Test-Suite -- Welche Herausforderungen gibt es? -- Ich habe bisher immer mit `unittest` gearbeitet. Gib gerne Hinweise auf Gemeinsamkeiten und Unterschiede zwischen `pytest` und `unittest`. -- Ergänze alle Erläuterungen mit praxisnahen Beispielen -``` \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/ruff.md b/notes/nextcloud/Notes/IT-Know-How/python/ruff.md deleted file mode 100755 index 1011cb1..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/ruff.md +++ /dev/null @@ -1,540 +0,0 @@ -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). \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/uv.md b/notes/nextcloud/Notes/IT-Know-How/python/uv.md deleted file mode 100755 index 0ec6bb6..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/uv.md +++ /dev/null @@ -1,94 +0,0 @@ -`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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/python/uvicorn.md b/notes/nextcloud/Notes/IT-Know-How/python/uvicorn.md deleted file mode 100755 index d16836b..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/python/uvicorn.md +++ /dev/null @@ -1,469 +0,0 @@ -## 1. Grundsätzliche Definition: Was ist **uvicorn**? - -**uvicorn** ist ein extrem performanter, asynchroner Web-Server für Python, der das **ASGI**‑Protokoll unterstützt. - -- **ASGI** = *Asynchronous Server Gateway Interface* -- uvicorn ist also das Bindeglied zwischen: - - dem Web (HTTP, WebSockets) - - und deiner Python‑Applikation (z.B. [[FastAPI]], Starlette, Django mit ASGI) - -Uvicorn basiert intern auf sehr schnellen C‑Bibliotheken: -- **uvloop** (schneller Event Loop, Ersatz für `asyncio`‑Loop) -- **httptools** (schnelles HTTP‑Parsing) - -Du verwendest uvicorn typischerweise, um eine ASGI‑App „zu starten“: - -```bash -uvicorn main:app --reload -``` - ---- - -## 2. Wichtige Begriffe: ASGI, WSGI und Web-Frameworks - -### 2.1 ASGI vs. WSGI - -- **WSGI** (älterer Standard, z.B. für Django (klassisch), Flask): - - synchron - - kein natives WebSocket‑Support - - typische Server: `gunicorn`, `uWSGI`, `mod_wsgi` - -- **ASGI** (moderner Standard): - - unterstützt **async/await** - - kann **HTTP** und **WebSockets** und Background Tasks - - typische Server: `uvicorn`, `hypercorn`, `daphne` - -uvicorn ist also ein **ASGI-Server**, nicht WSGI. - -### 2.2 uvicorn vs. Web-Frameworks ([[FastAPI]], Starlette, Django, Flask) - -- **Framework** ([[FastAPI]], Starlette, Django, Flask): - - definiert, wie du Routen, Views, Models, etc. schreibst. - - kümmert sich um Request/Response‑Logik - -- **uvicorn**: - - kümmert sich um das Annehmen von Verbindungen, HTTP‑Parsing, Event‑Loop‑Handling. - - ruft deine Applikation nur gemäß dem ASGI‑Protokoll auf. - -Bildlich: -**Browser** → (HTTP) → **uvicorn** → (ASGI) → **deine App** (z.B. [[FastAPI]]) - ---- - -## 3. Abgrenzung zu ähnlichen oder verwandten Begriffen - -### 3.1 uvicorn vs. Gunicorn - -- **gunicorn**: - - klassischer **WSGI**-Server (für sync‑Apps wie Flask oder Django ohne ASGI). - - kann aber mithilfe von Workern wie `uvicorn.workers.UvicornWorker` auch ASGI-App starten. - -Beispiel: [[FastAPI]]‑App mit gunicorn + uvicorn worker: - -```bash -gunicorn -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8000 -``` - -Hier ist: -- gunicorn = Prozessmanager und Worker‑Spawner -- uvicorn = eigentlicher ASGI‑Server pro Worker - -### 3.2 uvicorn vs. Hypercorn / Daphne - -- **hypercorn**: - - anderer ASGI‑Server (unterstützt z.B. HTTP/2, verschiedene Event Loops) -- **daphne**: - - ASGI‑Server aus dem Django‑Channels‑Ökosystem - -Alle drei (uvicorn, hypercorn, daphne) machen im Kern das Gleiche: -**ASGI‑Apps ausführen**, unterscheiden sich aber in Features, Performance und Konfigurationsmöglichkeiten. - -### 3.3 uvicorn vs. „eingebauter Development-Server“ - -Viele Frameworks haben eingebaute Dev-Server, z.B.: - -- Flask: `app.run(debug=True)` -- Django: `python manage.py runserver` - -Diese sind: -- für **Entwicklung** gedacht -- nicht für **Produktion** (Performance, Stabilität, Security) - -uvicorn ist ein **richtiger** Webserver, der für **Produktion** geeignet ist (oft zusammen mit einem Reverse Proxy wie [[Nginx]]). - ---- - -## 4. Welche Probleme löst uvicorn? - -### 4.1 Asynchrone Web‑Backends performant betreiben - -Mit ASGI kannst du: - -- `async def` Endpoints schreiben -- WebSockets nutzen -- viele gleichzeitige Requests mit einem Event Loop bedienen - -uvicorn ermöglicht dir, diese **asynchronen** Apps performant auszuliefern. - -Praxisnahes Beispiel ([[FastAPI]]): - -```python -# main.py -from fastapi import FastAPI -import asyncio - -app = FastAPI() - -@app.get("/items/{item_id}") -async def read_item(item_id: int): - await asyncio.sleep(1) # simuliert eine I/O-Operation - return {"item_id": item_id} -``` - -Starten mit uvicorn: - -```bash -uvicorn main:app --reload -``` - -uvicorn kümmert sich darum, dass mehrere Requests gleichzeitig abgearbeitet werden können, während `asyncio.sleep` nicht blockiert. - -### 4.2 WebSockets und Long-Lived Connections - -ASGI (und damit uvicorn) unterstützt **WebSockets** nativ, was mit WSGI nicht geht. - -Beispiel mit Starlette: - -```python -# main.py -from starlette.applications import Starlette -from starlette.responses import JSONResponse -from starlette.websockets import WebSocket -from starlette.routing import Route, WebSocketRoute - -async def homepage(request): - return JSONResponse({"hello": "world"}) - -async def websocket_endpoint(websocket: WebSocket): - await websocket.accept() - await websocket.send_text("Willkommen!") - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Du hast gesendet: {data}") - -routes = [ - Route("/", endpoint=homepage), - WebSocketRoute("/ws", endpoint=websocket_endpoint), -] - -app = Starlette(routes=routes) -``` - -Start: - -```bash -uvicorn main:app -``` - -Mit WSGI wäre so ein WebSocket‑Endpoint nicht möglich. - -### 4.3 Produktionstauglicher Server gegenüber Entwicklungsservern - -- Stabilität bei hoher Last -- Steuerung von: - - Anzahl Worker‑Prozesse - - Timeouts - - Logging -- Start via CLI, systemd, Docker, Kubernetes etc. - ---- - -## 5. Grundlegende Verwendung von uvicorn - -### 5.1 Installation - -```bash -pip install uvicorn -# optional: schnellere Variante mit C-Extensions -pip install "uvicorn[standard]" -``` - -`[standard]` installiert u.a. `uvloop` und `httptools`. - -### 5.2 Minimalbeispiel: Plain-ASGI-App - -Du kannst eine ASGI‑App auch ohne Framework schreiben: - -```python -# app.py -async def app(scope, receive, send): - assert scope["type"] == "http" - - # Request body lesen (vereinfachter Fall) - await receive() - - body = b"Hello, world" - headers = [(b"content-type", b"text/plain")] - - await send({ - "type": "http.response.start", - "status": 200, - "headers": headers, - }) - await send({ - "type": "http.response.body", - "body": body, - }) -``` - -Starten: - -```bash -uvicorn app:app --reload -``` - -Erklärung: -- `app:app` = Modul `app.py`, Variable `app` -- `--reload` = automatischer Neustart bei Codeänderung (nur dev) - -### 5.3 Beispiel mit [[FastAPI]] - -```python -# main.py -from fastapi import FastAPI - -app = FastAPI() - -@app.get("/") -async def root(): - return {"message": "Hello from uvicorn + FastAPI"} -``` - -Start: - -```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 -``` - -Wichtige CLI‑Optionen: -- `--reload`: Auto-Reload bei Codeänderungen (Dev) -- `--host`: z.B. `0.0.0.0` um von außen erreichbar zu sein -- `--port`: Port, z.B. `8000` -- `--workers`: Anzahl der Prozesse (für Produktion) - -### 5.4 Starten aus Python heraus - -```python -# run.py -import uvicorn - -if __name__ == "__main__": - uvicorn.run( - "main:app", - host="0.0.0.0", - port=8000, - reload=True, - ) -``` - -Start: - -```bash -python run.py -``` - ---- - -## 6. Typische Konfigurationen und Szenarien - -### 6.1 Entwicklung - -- ein Worker -- `--reload` aktiviert -- Logging auf `debug` - -```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 --log-level debug -``` - -### 6.2 Produktion (einfach) - -- mehrere Worker-Prozesse -- kein `--reload` -- Logging eher `info` oder `warning` - -```bash -uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 --log-level info -``` - -Richtwert für Worker: -`Anzahl CPU-Kerne * 2` (abhängig von App und Last; immer testen). - -### 6.3 Produktion hinter einem Reverse Proxy (z.B. [[Nginx]]) - -Typischer Aufbau: - -``` -Internet → Nginx (TLS, gzip, etc.) → uvicorn → FastAPI/Starlette/Django -``` - -- [[Nginx]] übernimmt TLS/SSL, Load Balancing, Static Files -- uvicorn macht die Application-Logik - -[[Nginx]]-Konfig (stark vereinfacht) könnte so aussehen: - -```nginx -location / { - proxy_pass http://127.0.0.1:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; -} -``` - -uvicorn wird weiterhin wie oben gestartet. - ---- - -## 7. Herausforderungen und typische Stolpersteine - -### 7.1 Async/Synchron-Mix und Blockierungen - -**Problem:** -Du verwendest uvicorn (ASGI, async), aber in deinen Endpoints gibt es blockierende Operationen: - -- große CPU‑Aufgaben -- synchrones Warten auf externe APIs (z.B. `requests.get(...)`) -- schwere Datenbank‑Queries, die nicht async sind - -Beispiel: - -```python -@app.get("/slow") -async def slow(): - import time - time.sleep(5) # BLOCKIERT den Event-Loop - return {"status": "ok"} -``` - -Folge: -- Ein Request blockiert den Event Loop → alle anderen Requests warten mit. - -Lösungen: -- I/O: async Libraries benutzen (z.B. `httpx` statt `requests`, `asyncpg` statt sync‑DB‑Client) -- CPU‑lastig: in Thread‑Pool oder Process‑Pool auslagern (`run_in_threadpool` etc.) - -### 7.2 Gemeinsamer Zustand über Worker-Prozesse - -Wenn du `--workers > 1` nutzt, hast du **mehrere Prozesse**. -Globaler Zustand in Python wird **nicht** zwischen Prozessen geteilt. - -Beispiel (Problem): - -```python -counter = 0 - -@app.get("/count") -def count(): - global counter - counter += 1 - return {"counter": counter} -``` - -Mit mehreren Workern: -- jeder Worker hat seinen eigenen `counter` -- Ergebnisse sind inkonsistent - -Lösung: -- geteilten Zustand über externe Systeme (Redis, Datenbank, etc.) -- oder nur einen Worker nutzen, wenn globaler In-Memory-State unvermeidbar ist (aber meist unsauber). - -### 7.3 Datenbankverbindungen und Lebenszyklus - -uvicorn unterstützt ASGI‑`lifespan`‑Events (startup/shutdown). -Frameworks wie [[FastAPI]]/Starlette nutzen das, um z.B. DB‑Connections zu öffnen/schließen. - -Stolpersteine: -- Verbindungspools pro Worker korrekt initialisieren -- bei Shutdown sauber schließen -- nicht „pro Request“ neue Connections aufmachen - -Beispiel mit [[FastAPI]] (vereinfacht): - -```python -from fastapi import FastAPI - -app = FastAPI() -db = None - -@app.on_event("startup") -async def startup(): - global db - db = await some_async_db_connect() - -@app.on_event("shutdown") -async def shutdown(): - await db.close() -``` - -### 7.4 Logging und Error-Handling - -uvicorn hat eigenes Logging; dein Framework ebenso. -Typische Themen: - -- Log-Format in Produktion standardisieren -- Fehler-Logs im Zusammenspiel mit Reverse Proxy -- Ausführliche Logs in Dev, weniger in Prod - -Beispiel (JSON-Logging in Produktion, nur angedeutet): - -```bash -uvicorn main:app \ - --host 0.0.0.0 \ - --port 8000 \ - --log-config logging_config.yaml -``` - -In `logging_config.yaml` kannst du detailliert das Logging steuern. - -### 7.5 Plattformunterschiede (Windows vs. Linux) - -- `--reload` nutzt File-Watcher und Signale → unter Linux sehr stabil; unter Windows kann es ein paar Besonderheiten geben. -- In Produktion läuft uvicorn meist auf Linux‑Servern oder in Docker‑Containern. - ---- - -## 8. Kurze Checkliste für den praktischen Einstieg - -1. **Framework wählen** - - [[FastAPI]] oder Starlette, wenn du intensiv async nutzen willst. -1. **App schreiben** - - `app = FastAPI()`, Endpoints definieren. -3. **In Entwicklung starten** - ```bash - uvicorn main:app --reload - ``` -4. **Vor Produktion** - - Blocking‑Code prüfen (CPU, I/O) - - Datenbankzugriff sauber konfigurieren (Pools, async‑Client) - - Logging und Error‑Handling aufräumen -5. **In Produktion starten** (einfach) - ```bash - uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 - ``` -6. **Optional**: - - vor uvicorn einen [[Nginx]] oder Traefik setzen (TLS, Load Balancing) - ---- - -## 9. Zusammenfassung - -- **uvicorn** ist ein **ASGI‑Webserver** für Python, optimiert für **asynchrone** Web‑Apps. -- Er ist **kein Web‑Framework**, sondern die Laufzeitumgebung für Frameworks wie **[[FastAPI]]**, **Starlette** oder moderne **Django**‑Konfigurationen. -- Es löst die Probleme klassischer WSGI‑Server in Bezug auf **Async**, **WebSockets** und **Performance**. -- Typische Herausforderungen liegen im Bereich: - - korrektes Async‑Design - - Umgang mit mehreren Workern und gemeinsamem Zustand - - saubere Integration von Datenbanken, Logging, Deployment -- Für dich als Python‑Entwickler ist uvicorn im Alltag vor allem: - **das Kommando, mit dem du deine moderne Web‑API startest.** - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Argo CD.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Argo CD.md deleted file mode 100755 index 4ab950d..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Argo CD.md +++ /dev/null @@ -1,358 +0,0 @@ -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 ` -- 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 - - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Ingress.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Ingress.md deleted file mode 100755 index 2d4ac78..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Ingress.md +++ /dev/null @@ -1,255 +0,0 @@ -Im IT-Kontext bedeutet **Ingress** allgemein: -> „Eingehender Datenverkehr in ein System, Netzwerk oder einen Dienst.“ - -Je nach Kontext wird der Begriff etwas unterschiedlich verwendet: -# Allgemein - -### 1. Netzwerk / Security - -In klassischen Netzwerken und Firewalls unterscheidet man: - -- **Ingress-Traffic**: Datenverkehr, der **von außen nach innen** in ein Netzwerk oder System hineinkommt - (z. B. HTTP-Anfragen aus dem Internet an Ihren Webserver). -- **Egress-Traffic**: Datenverkehr, der **von innen nach außen** geht - (z. B. Ihr Server ruft eine externe API im Internet auf). - -Typische Themen rund um Ingress in diesem Kontext: -- **Ingress-Filterung**: Regeln, die festlegen, welcher eingehende Traffic erlaubt ist (z. B. Firewall-Regeln). -- **Ingress-Schutz**: Maßnahmen gegen Angriffe von außen (DDoS-Schutz, WAF, Rate Limiting). - -### 2. Kubernetes (Spezialfall „Ingress“ Resource) - -In Kubernetes ist **Ingress** ein eigener Begriff und bezeichnet eine **API-Ressource**, die regelt, wie externe Requests in den Cluster hinein zu den internen Services geleitet werden. - -Kurz gesagt: -- Ein **Ingress** ist eine Art **Reverse Proxy / Routing-Regel** auf Layer 7 (HTTP/HTTPS). -- Er definiert z. B.: - - Welche Domain (`host`) auf welchen Service zeigt - - Welche Pfade (`/api`, `/app`) zu welchen Services geroutet werden - - SSL/TLS-Termination (HTTPS) - -Beispielhafte Ingress-Definition in Kubernetes: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-app-ingress -spec: - rules: - - host: myapp.example.com - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: my-api-service - port: - number: 80 -``` - -Hier sorgt der Ingress dafür, dass Anfragen an `https://myapp.example.com/api` in den Cluster gehen und beim Service `my-api-service` landen. - ---- - -Im Kubernetes-Kontext ist „Ingress“ etwas spezieller als nur „eingehender Traffic“. Es gibt drei eng zusammenhängende Dinge: - -1. **Ingress-Ressource** (YAML-Objekt, das Routing-Regeln beschreibt) -2. **Ingress-Controller** (Software/Pod, der diese Regeln umsetzt) -3. **(Optional) IngressClass** (welcher Controller für welches Ingress zuständig ist) - -Ich gehe der Reihe nach durch. - ---- -# Kubernetes -## 1. Warum braucht man Ingress überhaupt? - -Ohne Ingress hast du im Wesentlichen drei Möglichkeiten, Dienste nach außen verfügbar zu machen: - -- `ClusterIP` (Standard): nur intern im Cluster erreichbar -- `NodePort`: öffnet einen Port auf jedem Node; eher low-level / unkomfortabel -- `LoadBalancer`: Cloud-Loadbalancer vor einem Service (oft pro Service ein eigener LB) - -Nachteile: -- Viele externe IPs / Loadbalancer, wenn du viele Services hast -- Keine zentrale Layer‑7-Logik (z. B. Routing per Hostname/URL-Pfad, TLS-Termination) - -**Ingress** löst genau das: - -> Ein (oder wenige) externe Einstiegspunkte, die per Hostname und Pfad auf viele interne Services routen können, inkl. HTTPS/TLS, Auth, Rate-Limiting (je nach Controller). - ---- - -## 2. Ingress-Ressource (das YAML-Objekt) - -Die Ingress-Ressource beschreibt **Deklarativ**, wie Anfragen von außen zu Services im Cluster geroutet werden sollen: - -- Auf Basis von **Hostname** (`host`, z. B. `api.example.com`) -- Auf Basis von **Pfad** (`path`, z. B. `/api`, `/app`) -- Verknüpft mit einem **Backend-Service** (`Service` + Port) -- Optional: **TLS**-Konfiguration (Zertifikate) - -Beispiel: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-app-ingress - namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / -spec: - ingressClassName: nginx - tls: - - hosts: - - myapp.example.com - secretName: myapp-tls-secret - rules: - - host: myapp.example.com - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: my-api-service - port: - number: 80 - - path: /app - pathType: Prefix - backend: - service: - name: my-frontend-service - port: - number: 80 -``` - -Was hier passiert: - -- Alle Anfragen an `https://myapp.example.com/api...` → Service `my-api-service` -- Alle Anfragen an `https://myapp.example.com/app...` → Service `my-frontend-service` -- TLS-Zertifikat liegt in `Secret myapp-tls-secret` -- `ingressClassName: nginx` sagt: „Dieser Ingress ist für den Ingress-Controller mit Klasse `nginx` gedacht“ - -Wichtige Felder: - -- `rules[].host`: auf welchen Host gilt die Regel? -- `rules[].http.paths[].path`: URL-Pfad -- `pathType`: - - `Prefix` → „beginnt mit diesem Pfad“ - - `Exact` → „genau dieser Pfad“ - - (früher gab es noch `ImplementationSpecific`) -- `backend.service.name` / `port`: zu welchem Service leiten? - ---- - -## 3. Ingress-Controller (die „Engine“ dahinter) - -Die YAML-Ressource alleine macht noch nichts. Es braucht einen **Ingress-Controller**: - -- Läuft als Pod/Deployment im Cluster -- Beobachtet (watch) Ingress-Ressourcen im API-Server -- Übersetzt die Regeln in eine konkrete Konfiguration (z. B. [[Nginx]], Envoy, Traefik …) -- Lauscht auf einem (oder mehreren) Node-Ports/LoadBalancer-IPs und routet Traffic in die passende Services - -Typische Implementierungen: - -- **[[Nginx]] Ingress Controller** -- **Traefik** -- **HAProxy Ingress** -- **Envoy-basierte** Controller -- Cloud-spezifische: - - GCE/GLBC (GKE HTTP(S) Load Balancing) - - AWS ALB Ingress Controller - - Azure Application Gateway Ingress - -Wichtig: -Du kannst mehrere Ingress-Controller im Cluster haben, z. B. einen für öffentliche, einen für interne Services. - ---- - -## 4. IngressClass (Zuordnung Controller ↔ Ingress) - -Mit `IngressClass` kann man genau definieren, **welcher** Ingress-Controller für welche Ingress-Ressourcen zuständig ist. - -Beispiel IngressClass: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: nginx -spec: - controller: k8s.io/ingress-nginx -``` - -Im Ingress verknüpfst du das mit: - -```yaml -spec: - ingressClassName: nginx - # ... -``` - -Somit: - -- Nur der Ingress-Controller, der sich als `k8s.io/ingress-nginx` registriert, reagiert auf diesen Ingress. -- Andere Controller ignorieren ihn. - -Früher wurde das oft über **Annotations** geregelt (`kubernetes.io/ingress.class`), `ingressClassName` ist der neuere, bevorzugte Weg. - ---- - -## 5. Request-Flow: Wie läuft eine Anfrage technisch? - -Typischer Ablauf (z. B. mit [[Nginx]] Ingress Controller in einer Cloud): - -1. Benutzer ruft im Browser `https://myapp.example.com/api/users` auf. -2. DNS zeigt `myapp.example.com` auf die externe IP eines Cloud-Loadbalancers. -3. Der Cloud-Loadbalancer leitet die Anfrage an einen Node/Port, auf dem der Ingress-Controller erreichbar ist. -4. Der Ingress-Controller (Pod) erhält die HTTP(S)-Anfrage. -5. Er vergleicht Host (`myapp.example.com`) und Pfad (`/api/users`) mit seinen Ingress-Regeln: - - Host passt zu `myapp.example.com` - - Pfad passt zum Prefix `/api` -6. Er leitet die Anfrage intern an den Kubernetes-Service `my-api-service:80` weiter. -7. Kubernetes-Service verteilt die Anfrage per kube-proxy/IPVS/iptables auf einen Pod (`my-api`-Pod). -8. Antwort geht den Weg zurück und landet beim Benutzer. - ---- - -## 6. Typische Features (Controller-abhängig) - -Je nach Ingress-Controller gibt es zusätzliche Möglichkeiten, meist über **Annotations** oder spezifische CRDs: - -- **TLS-Termination** und optional TLS-Passthrough -- **HTTP → HTTPS Redirect** -- **Path-Rewrites**, z. B. `/api` außen → `/` im Backend -- **Authentifizierung** (z. B. OAuth2, Basic Auth) -- **Rate Limiting**, IP-Whitelist/Blacklist -- **Canary-Deployments / Traffic-Splitting** -- **gRPC-Support**, WebSockets, etc. - -Beispiel (nginx Spezifische Annotation für Redirect): - -```yaml -metadata: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" -``` - ---- - -## 7. Grenzen von Ingress & Gateway API - -Die klassische Ingress-API ist bewusst relativ simpel (Host, Pfad, Backend, TLS). -Für komplexere Szenarien (multi-tenant, umfangreiche Routing-Logik, mehrere Gateways) gibt es inzwischen die **Gateway API** (Ressourcen wie `Gateway`, `HTTPRoute` etc.), die Ingress langfristig ergänzen/teilweise ablösen soll. - -Dennoch ist Ingress aktuell (2024) in vielen Clustern der Standardweg, HTTP/HTTPS-Traffic von außen hereinzubringen. - ---- - -Wenn du möchtest, kann ich als Nächstes: - -- ein konkretes Setup „von Null an“ durchgehen (z. B. Minikube + [[Nginx]] Ingress + Beispiel-App), oder -- spezifisch auf Annotations/Features eines bestimmten Ingress-Controllers eingehen (etwa nginx oder Traefik). \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/JWT token.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/JWT token.md deleted file mode 100755 index a1f465a..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/JWT token.md +++ /dev/null @@ -1,427 +0,0 @@ -Ein JSON Web Token (JWT) wirkt am Anfang oft etwas „magisch“. Wenn man es einmal verstanden hat, ist es aber ein relativ simples Baustein-konzept für Authentifizierung und Autorisierung in modernen Web‑ und API‑Systemen. - -Ich gehe Schritt für Schritt durch: - -1. Grundidee: Was ist ein JWT? -2. Aufbau eines JWT -3. Abgrenzung zu verwandten Konzepten -4. Welche Probleme werden mit JWT gelöst? -5. Typische Anwendungsfälle (praxisnah erklärt) -6. Herausforderungen und Fallstricke -7. Kurz-Fazit - ---- - -## 1. Grundidee: Was ist ein JWT? - -**Definition (vereinfacht):** - -Ein **JWT (JSON Web Token)** ist ein **kompakter, URL-tauglicher, digital signierter Datenblock**, der Informationen über einen Benutzer oder eine Aktion enthält (z.B. „Benutzer X ist eingeloggt und hat Rolle admin“). -Diese Informationen sind im JSON-Format strukturiert und werden mit einem kryptografischen Verfahren signiert (und optional verschlüsselt). - -Damit kann ein Server (oder mehrere Server) **prüfen**, ob das Token: -- von einem vertrauenswürdigen Aussteller stammt, -- nicht manipuliert wurde, -- noch gültig ist (nicht abgelaufen). - -Man kann sich einen JWT grob als „fälschungssichereren Ausweis“ für Benutzer (oder Clients) vorstellen, der vom Server ausgestellt wird und den der Client bei weiteren Anfragen vorzeigt. - ---- - -## 2. Aufbau eines JWT - -Ein JWT besteht aus **drei Teilen**, jeweils Base64URL-kodiert, durch Punkte getrennt: - -```text -HEADER.PAYLOAD.SIGNATURE -``` - -Ein Beispiel (gekürzt, sieht in der Praxis ähnlich aus): - -```text -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 -. -eyJzdWIiOiIxMjM0NTYiLCJlbWFpbCI6Im1heC5tdXN0ZXJAbWFpbC5jb20iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MzAxNjkzNDN9 -. -dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk -``` - -### 2.1 Header - -Der Header beschreibt u.a.: -- welches Signaturverfahren genutzt wird, -- dass es sich um ein JWT handelt. - -Beispiel: - -```json -{ - "alg": "HS256", // Algorithmus: HMAC-SHA256 - "typ": "JWT" -} -``` - -### 2.2 Payload (Claims) - -Die Payload enthält die eigentlichen Informationen („Claims“). -Beispiele: - -```json -{ - "sub": "123456", // subject: Benutzer-ID - "email": "max.mustermann@example.com", - "role": "admin", - "iat": 1730169334, // issued at (Unix-Timestamp) - "exp": 1730172934, // expiry: Ablaufzeitpunkt - "iss": "https://auth.meine-app.de", // issuer - "aud": "https://api.meine-app.de" // audience -} -``` - -Wichtige Standard-Claims (nicht verpflichtend, aber üblich): -- `iss` – Issuer (Wer hat das Token ausgestellt?) -- `sub` – Subject (Auf wen bezieht sich das Token? z.B. Benutzer-ID) -- `aud` – Audience (Für welche Anwendung/Services ist das Token bestimmt?) -- `exp` – Expiration Time (Wann läuft das Token ab?) -- `iat` – Issued At (Wann wurde es ausgestellt?) -- `nbf` – Not Before (Ab wann ist es gültig?) - -Dazu kommen **anwendungsspezifische Claims**, z.B.: -- `email`, `username` -- `roles`: `["admin", "user"]` -- `scope`: `"read:orders write:orders"` - -### 2.3 Signature - -Die Signatur wird aus dem Header + Payload + einem Geheimnis (oder Schlüssel) gebildet. -Formel grob: - -```text -signature = Sign( base64Url(header) + "." + base64Url(payload), secret_or_private_key ) -``` - -Beim Prüfen: -- rekonstruiert der Server diese Signatur aus Header+Payload, -- vergleicht sie mit der Signatur im Token, -- wenn sie übereinstimmen → Token wurde nicht manipuliert. - ---- - -## 3. Abgrenzung zu verwandten Begriffen - -### 3.1 JWT vs klassische Session-Cookies - -**Klassische Session:** -- Nach Login erzeugt der Server eine **Session-ID** und speichert alle Infos (Benutzer, Rollen usw.) **im Server-Speicher** (in Memory, Redis, DB). -- Die Session-ID wird im Browser z.B. als Cookie gespeichert. -- Bei jeder Anfrage sendet der Browser die Session-ID; Server schaut im eigenen Speicher nach, welche Daten dazu gehören. - -**JWT:** -- Nach Login erzeugt der Server ein **Token**, das die Infos (Benutzer, Rollen, Ablaufzeit) **direkt im Token** enthält. -- Das Token wird typischerweise im Browser (Cookie oder Speicher der App) gehalten. -- Bei jeder Anfrage schickt der Client das Token mit (z.B. via HTTP-Header `Authorization: Bearer `). -- 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 -``` - -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 `. -- `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= -``` - -4. Benutzer klickt den Link. -5. Frontend schickt den Token an das Backend, z.B. POST `/auth/reset-password`. -6. Backend prüft: - - Signatur, - - `exp` (noch gültig?), - - `purpose == "password_reset"`. -7. Wenn alles passt → Passwort darf geändert werden. - ---- - -## 6. Herausforderungen und Fallstricke - -JWTs lösen einige Probleme, bringen aber eigene Herausforderungen mit sich. - -### 6.1 Sicherheit bei Signatur und Schlüssel - -Wichtige Punkte: - -- **Algorithmuswahl:** - Typisch: - - Symmetrisch: `HS256` (ein gemeinsames Secret für Signatur & Prüfung) - - Asymmetrisch: `RS256` (privater Schlüssel signiert, öffentlicher Schlüssel prüft) -- **Keys sicher verwalten:** - - nicht im Code hardcoden, - - in sicheren Secret-Stores halten, - - Schlüssel regelmäßig rotieren. - -Gefährliche Anti-Pattern: -- `alg: none` akzeptieren (nie tun), -- Server so konfigurieren, dass er das `alg` aus dem Token blind vertraut (statt fest vorzugeben, was erlaubt ist). - -### 6.2 Token-Ablauf und „Logout“ - -JWT ist stateless – das macht „Logout“ tricky: - -- Klassische Session: - - Session im Server-Speicher löschen → Benutzer abgemeldet. -- JWT: - - Token ist selbstständig gültig, bis `exp` erreicht ist. - - Serverseitig gibt es **keine zentrale Liste** aller aktiven JWTs. - -Mögliche Lösungen: -- Tokens **kurzlebig** machen, z.B. 5–15 Minuten. -- Für längere Sessions: - - **Access Token** kurzlebig, - - **Refresh Token** längerlebig und serverseitig verwaltbar (z.B. in DB), - - bei Logout → Refresh Token invalidieren, Access Token läuft automatisch bald ab. -- Optional: Blacklists/Blocklists oder Token-Revocation-Mechanismen. - -### 6.3 Wo speichert man JWT im Browser? - -Varianten: -- **httpOnly Cookie**: - - Vorteil: nicht via JavaScript auslesbar → Schutz gegen XSS. - - Nachteil: CSRF muss beachtet werden (CSRF-Protection implementieren). -- **localStorage / sessionStorage**: - - Vorteil: einfache Handhabung im JS-Code. - - Nachteil: bei XSS kann ein Angreifer das Token auslesen → sehr riskant. - -In der Praxis: -- Oft Kombination aus httpOnly Cookies + CSRF-Schutz, -- oder Speziallösungen mit sog. „Double Submit Cookies“, „SameSite“-Cookies etc. - -### 6.4 Token sind nicht geheim (wenn nur signiert) - -Wichtiger Punkt: -- Ein **normaler signierter JWT ist nicht verschlüsselt**. -- Jeder, der das Token sieht, kann die Payload (Claims) lesen, da sie Base64URL-kodiert ist (was keine Verschlüsselung ist). - -Daraus folgt: -- Keine hochsensiblen Daten in die Payload packen (z.B. Passwörter, Zahlungsdaten, interne Geheimnisse). -- JWT nur über **HTTPS** übertragen, damit niemand auf dem Weg „mithören“ kann. -- Wenn wirklich Geheimhaltung nötig ist → **JWE (verschlüsseltes JWT)** oder andere Verschlüsselungsmechanismen. - -### 6.5 Token-Größe - -Wenn man sehr viele Claims ins Token packt: -- wird es groß, -- verursacht mehr Traffic, -- passt evtl. schlechter in Cookies oder Header. - -Praktisch: -Nur wirklich relevante Informationen ins JWT aufnehmen: -- Benutzer-ID -- Rollen, Rechte oder Scope -- wenige zusätzliche Angaben - -Den Rest kann man bei Bedarf aus der Datenbank abfragen. - -### 6.6 Fehler bei der Prüfung des Tokens - -Beim Validieren darf man nicht nur die Signatur prüfen, sondern auch: - -- Ist das Token abgelaufen? (`exp`) -- Ist das Token noch nicht gültig? (`nbf`, `iat`) -- Stimmt `iss` (Issuer) mit dem erwarteten Aussteller überein? -- Stimmt `aud` (Audience) mit der eigenen Anwendung/API überein? - -Beispiel (Node.js mit `jsonwebtoken`): - -```js -const jwt = require('jsonwebtoken'); - -const token = '...'; // vom Client erhalten -const publicKey = '...'; // Public Key (bei RS256) oder Secret (bei HS256) - -try { - const payload = jwt.verify(token, publicKey, { - algorithms: ['RS256'], - issuer: 'https://auth.meine-app.de', - audience: 'https://api.meine-app.de', - }); - - console.log('Token gültig, Benutzer-ID:', payload.sub); -} catch (err) { - console.error('Ungültiges Token:', err.message); -} -``` - ---- - -## 7. Kurz-Fazit - -- Ein **JWT** ist ein **signierter, kompakter Datenträger im JSON-Format**, der Claims über Benutzer/Clients enthält. -- Er wird verwendet für: - - Authentifizierung (z.B. „Benutzer X ist eingeloggt“), - - Autorisierung (z.B. „Benutzer X hat die Rolle admin“), - - zeitlich begrenzte Aktionen (Passwort-Reset, E-Mail-Bestätigung), - - verteilte Systeme (Microservices), die ohne zentralen Session-Speicher auskommen sollen. -- JWTs sind besonders sinnvoll in: - - Web-APIs, - - SPAs, - - mobilen Apps, - - Microservice- und Cloud-Architekturen. -- Abgrenzung: - - JWT ≠ OAuth2/OIDC (Protokoll vs. Tokenformat), - - JWT ≠ Session-Cookie (zustandslos vs. zustandsbehaftet), - - JWTs sind meist JWS (signiert, aber nicht verschlüsselt). -- Herausforderungen: - - Sichere Schlüsselverwaltung, korrekte Token-Prüfung, - - Umgang mit Logout/Token-Invalidierung, - - sicherer Speicherort im Client (XSS/CSRF), - - kein Hineinpacken hochsensibler Daten. - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md deleted file mode 100755 index 2f15627..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md +++ /dev/null @@ -1,512 +0,0 @@ -## 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 ` - -#### 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 ` - -#### 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 ` - -#### 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 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 -n -``` - -**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 -``` - -**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 -n -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 svc/ : -``` - -**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 -n -``` - -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: - ``` - 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 -n -``` - -2. **Direkt mit curl testen:** -```bash -curl -H "Host: openmetadata.dma.aai-dfine.de" http:// -``` - -**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 ` -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 -n - -# Letzte 100 Zeilen -kubectl logs -n --tail=100 - -# Vorheriger Container (wenn Pod neu gestartet wurde) -kubectl logs -n --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 - -# 2. Sind Endpoints vorhanden? -kubectl get endpoints -n - -# 3. Service-Details -kubectl describe svc -n -``` - ---- - -### Problem: Authentifizierung erforderlich - -**Wenn Login-Seite erscheint:** - -**Standard-Credentials suchen:** -```bash -# Secrets auflisten -kubectl get secrets -n - -# Secret anschauen (Werte sind base64-kodiert) -kubectl get secret -n -o yaml - -# Secret dekodieren -kubectl get secret -n -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 - -# Alle Ingress-Endpoints -kubectl get ingress --all-namespaces -``` - -### Status prüfen -```bash -# Pods mit mehr Details -kubectl get pods -n -o wide - -# Pod-Logs live verfolgen -kubectl logs -f -n - -# In Pod einloggen (für Debugging) -kubectl exec -it -n -- /bin/bash -``` - -### Konfiguration anschauen -```bash -# YAML eines Ingress -kubectl get ingress -n -o yaml - -# Service-Details -kubectl describe svc -n - -# ConfigMap auslesen -kubectl get configmap -n -o yaml -``` - -### Port-Forwarding -```bash -# Standard -kubectl port-forward -n svc/ : - -# Zu einem Pod (falls Service nicht existiert) -kubectl port-forward -n : - -# Alle Interfaces binden (auch im Netzwerk erreichbar) -kubectl port-forward --address 0.0.0.0 -n svc/ 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 - ``` - -3. **Port-Forwarding starten:** - ```bash - kubectl port-forward -n svc/ 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`* diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes.md deleted file mode 100755 index 72c4842..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Kubernetes.md +++ /dev/null @@ -1,299 +0,0 @@ -#IT #Container #Open-Source #Cloud - ---- - -## 1. Was ist Kubernetes – in einem Satz? - -Kubernetes ist ein Open-Source‑System zur **Orchestrierung von Containern** (z. B. Docker), das die **Bereitstellung, Skalierung und Verwaltung** von Anwendungen automatisiert. - -- Entwickelt von Google, heute von der CNCF (Cloud Native Computing Foundation) verwaltet -- Läuft in der Cloud (AWS, Azure, GCP), im Rechenzentrum (On‑Prem) oder lokal (z. B. mit kind, minikube) - ---- - -## 2. Warum braucht man Kubernetes? - -### 2.1 Ausgangslage ohne Kubernetes - -Bei klassischen Server- oder Container-Setups müssen viele Dinge manuell oder mit Skripten gelöst werden: - -- Container starten/stoppen -- Lastverteilung zwischen Instanzen -- Neustart fehlerhafter Instanzen -- Rollout neuer Versionen -- Skalierung bei Lastspitzen -- Konfigurationsmanagement und Secrets -- Self-Healing bei Fehlern - -### 2.2 Ziele von Kubernetes - -Kubernetes löst diese Probleme, indem es: - -1. **Deklaratives Modell** nutzt - - Du beschreibst den „Soll-Zustand“ (z. B. „es sollen 3 Instanzen meiner App laufen“). - - Kubernetes sorgt im Hintergrund dafür, dass dieser Zustand erreicht und gehalten wird. - -2. **Automatisierung** von: - - Rollouts und Rollbacks - - Skalierung (horizontaler Ausbau: mehr Instanzen) - - Self-Healing (Pods werden bei Absturz neu gestartet) - - Service Discovery & Load Balancing - -3. **Abstraktion von Infrastruktur**: - - Egal ob deine Knoten VMs in der Cloud oder bare-metal-Server sind – Kubernetes abstrahiert das weg. - ---- - -## 3. Hohe Ebene: Aufbau eines Kubernetes-Clusters - -Ein Kubernetes-Cluster besteht grob aus: - -- **Control Plane** (Master-Komponenten) -- **Worker Nodes** (wo die Container wirklich laufen) - -### 3.1 Control Plane (Steuerungsebene) - -Wichtige Komponenten: - -- **kube-apiserver** - - Zentraler Einstiegspunkt - - Alle Befehle von `kubectl` und anderen Komponenten laufen über die API ([[kubectl]]) - -- **etcd** - - Verteilter Key-Value-Store - - Hält den gesamten Cluster-Zustand (z. B. welche Pods, Deployments, ConfigMaps etc.) - -- **kube-scheduler** - - Entscheidet, auf welchem Node neue Pods laufen sollen - - Berücksichtigt Ressourcen (CPU, RAM), Constraints, Affinities etc. - -- **kube-controller-manager** - - Mehrere Controller (z. B. für Deployments, Nodes, Endpoints) - - Stellt sicher, dass der Ist-Zustand dem Soll-Zustand entspricht („Reconciliation Loop“) - -- **cloud-controller-manager** (optional) - - Integration mit Cloud-Anbietern (z. B. Load Balancer in AWS/Azure/GCP anlegen) - -### 3.2 Worker Nodes (Arbeitsebene) - -Auf jedem Worker laufen: - -- **kubelet** - - Agent auf dem Node - - Kommuniziert mit der API und sorgt lokal dafür, dass die Pods wie gewünscht laufen - -- **Container Runtime** - - Führt Container aus: z. B. containerd, CRI‑O, Docker (früher direkt, heute über CRI) - -- **kube-proxy** - - Implementiert das Kubernetes-Service-Netzwerk - - Kümmert sich um Weiterleitung von Netzwerkpaketen zu den richtigen Pods - ---- - -## 4. Zentrale Konzepte in Kubernetes - -### 4.1 Pod - -- Kleinste deploybare Einheit in Kubernetes -- Ein Pod enthält einen oder mehrere Container, die: - - sich IP-Adresse und Ports teilen - - sich lokale Volumes teilen -- Typischerweise: 1 Container pro Pod (plus evtl. Sidecars) - -Beispiel: Ein Pod mit einem [[Nginx]] Container: - -```yaml -apiVersion: v1 -kind: Pod -metadata: - name: my-nginx -spec: - containers: - - name: nginx - image: nginx:latest - ports: - - containerPort: 80 -``` - -### 4.2 Deployment - -- Deklarative Beschreibung von Pods + Skalierung -- Sagt z. B.: „Es sollen 3 identische Replikate dieses Pods laufen“ -- Unterstützt Rollouts und Rollbacks (z. B. `kubectl rollout undo`) - -Beispiel-Deployment: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-nginx-deployment -spec: - replicas: 3 - selector: - matchLabels: - app: my-nginx - template: - metadata: - labels: - app: my-nginx - spec: - containers: - - name: nginx - image: nginx:1.27 - ports: - - containerPort: 80 -``` - -### 4.3 Service - -- Stabile Netzwerk-Adresse (DNS-Name, virtuelle IP) für eine Gruppe von Pods -- Entkoppelt Clients von einzelnen Pod-IP-Adressen -- Typen: - - **ClusterIP**: intern im Cluster - - **NodePort**: nach außen freigegeben über einen Port jedes Nodes - - **LoadBalancer**: Integration mit einem Cloud-Load-Balancer - -Beispiel-Service: - -```yaml -apiVersion: v1 -kind: Service -metadata: - name: my-nginx-service -spec: - type: ClusterIP - selector: - app: my-nginx - ports: - - port: 80 - targetPort: 80 -``` - -### 4.4 Namespace - -- Logische Unterteilung innerhalb eines Clusters -- Dient zur: - - Trennung von Umgebungen (dev, test, prod) - - Multi-Tenancy - - Ressourcenkontrolle (ResourceQuotas, Limits) -- Standard-Namespace: `default`, daneben z. B. `kube-system`, `kube-public` - -### 4.5 ConfigMap & Secret - -- **ConfigMap**: Konfiguration (nicht sensibel), z. B. Properties, ENV-Variablen -- **Secret**: sensible Daten (Passwörter, Tokens, Zertifikate) -- Werden in Pods als ENV oder als Files (Volumes) bereitgestellt - -### 4.6 Volume & PersistentVolume - -- **Volumes**: Daten, die nicht verschwinden, wenn ein Container neu startet -- **PersistentVolume (PV)**: repräsentiert eine physische/Cloud-Speicher-Ressource -- **PersistentVolumeClaim (PVC)**: „Anforderung“ eines Pods an Speicher (Größe, Klasse) - ---- - -## 5. Wie arbeitet Kubernetes? – Das „Desired State“-Modell - -1. Du definierst Ressourcen (YAML/JSON), z. B.: - - - „Deployment mit 3 Replikas meiner App“ - - „Service, der auf Port 80 erreichbar ist“ - -2. Du übernimmt diese Definition per: - - ```bash - kubectl apply -f deployment.yaml - ``` - -3. Die API speichert diese Definition in **etcd**. - -4. Controller und Scheduler beobachten den Zustand und führen eine **Reconciliation Loop** aus: - - - Ist-Zustand (z. B. 2 Pods laufen) vs. Soll-Zustand (3 Pods sollen laufen) - - Kubernetes startet einen weiteren Pod - - Fällt ein Pod aus → wird automatisch ersetzt - ---- - -## 6. Typischer Entwickler-Workflow mit Kubernetes - -1. **Container-Image bauen** - - z. B. mit Docker oder Buildpacks - -2. **Image registrieren** - - In einer Container Registry (Docker Hub, GitHub Container Registry, ECR, GCR…) - -3. **Kubernetes-Manifest(e) schreiben** - - Deployment, Service, ConfigMap, Secret, etc. - -4. **Deployen** - - `kubectl apply -f .` - - Oder über CI/CD-Pipelines (GitLab CI, GitHub Actions, Argo CD, Jenkins etc.) - -5. **Monitoring & Logging** - - Über Tools wie Prometheus, Grafana, ELK/EFK-Stack, Loki, etc. - ---- - -## 7. Kubernetes im Ökosystem / Erweiterungen - -Kubernetes ist eine Basis, auf der vieles aufbaut: - -- **Helm** - - „Package Manager“ für Kubernetes - - Erleichtert das Installieren komplexer Anwendungen (Charts) - -- **Ingress & Ingress Controller** - - HTTP/HTTPS-Routing auf Services - - Ermöglicht virtuelle Hosts, Pfad-basiertes Routing usw. - -- **Service Mesh (z. B. Istio, Linkerd)** - - Feinere Kontrolle über Traffic, Observability, Security (mTLS) - -- **Operators** - - Kubernetes-Erweiterungen, die komplexe Anwendungen als „Custom Resources“ managen (z. B. Datenbanken) - -- **GitOps (z. B. Argo CD, Flux)** - - Zustände des Clusters werden aus Git-Repositories synchronisiert - ---- - -## 8. Vorteile von Kubernetes - -- Hohe **Portabilität** über verschiedene Infrastrukturen -- **Automatisierte Skalierung** und Self-Healing -- **Standardisierte Schnittstellen** (API-first) -- Große **Community** und reiches Ökosystem -- Gut geeignet für **Microservices** und verteile Systeme - ---- - -## 9. Herausforderungen und Komplexität - -- **Steile Lernkurve**: viele Konzepte (Pods, Deployments, Services, Ingress, RBAC, Storage…) -- **Betrieb** eines eigenen Clusters ist nicht trivial - - Viele Unternehmen nutzen Managed Services: GKE, AKS, EKS, OpenShift, Rancher etc. -- **Observability**: Logs, Metrics, Tracing sind komplexer als bei einer einzigen VM -- **Sicherheit** (RBAC, NetworkPolicies, Secrets-Management) erfordert Planung - ---- - -## 10. Wie kann man praktisch starten? - -Konkrete Schritte, um anzufangen: - -1. **Lokale Umgebung**: - - `kind` (Kubernetes in Docker) - - `minikube` oder `k3d` - -2. **Übungen**: - - Einen einfachen Webserver als Deployment & Service bereitstellen - - Mit `kubectl get`, `describe`, `logs` spielen - - Scaling testen: `kubectl scale deployment my-deployment --replicas=5` - - Eine neue Version deployen und Rollback ausprobieren - -3. **Doku & Lernressourcen**: - - Offizielle Doku: https://kubernetes.io/docs/ - - Interaktive Tutorials: Katacoda-ähnliche Labs, Kubernetes Bootcamps, z. B. von Cloud-Anbietern diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Layer 7 Logik.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Layer 7 Logik.md deleted file mode 100755 index 82a0ee3..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Layer 7 Logik.md +++ /dev/null @@ -1,25 +0,0 @@ -„Layer‑7‑Logik“ bezieht sich auf Entscheidungen/Routing auf Basis der **Anwendungsebene** (Layer 7 im OSI-Modell). - -Kurz zum Kontext: - -- **Layer 3 (Netzwerk)**: IP-Adressen -- **Layer 4 (Transport)**: TCP/UDP, Ports (z. B. Port 80, 443) -- **Layer 7 (Anwendung)**: HTTP, HTTPS, gRPC, SMTP etc. – also „Inhalt“ der Anfrage - -**Layer‑4‑Routing**: -Es wird nur nach IP + Port entschieden. Beispiel: -- Alles an `10.0.0.5:80` geht zu Service A. -Der Proxy/Loadbalancer „sieht“ nicht, ob das `/api` oder `/shop` ist. - -**Layer‑7‑Logik**: -Der Proxy versteht das Protokoll (z. B. HTTP) und kann anhand von Anwendungsdaten entscheiden: - -- Hostname: `api.example.com` → Service A, `shop.example.com` → Service B -- Pfad: `/api` → Backend 1, `/app` → Backend 2 -- HTTP-Header: bestimmter `User-Agent`, `X-Feature-Flag` → anderes Routing -- Cookies / Session: z. B. „User ist in A/B-Test-Gruppe → anderes Backend“ -- Authentifizierung / [[JWT token]] prüfen, bevor weitergeleitet wird -- TLS-Termination: HTTPS entschlüsseln, weiter innen nur HTTP sprechen - -Im Kubernetes-Ingress-Kontext heißt „Layer‑7‑Logik“ also: -Der Ingress-Controller trifft Routing- und Sicherheitsentscheidungen **auf Basis von HTTP(S)-Details** (Host, Pfad, Header, Cookies usw.), nicht nur auf Basis von IPs und Ports. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Nginx.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Nginx.md deleted file mode 100755 index 212c0d1..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Nginx.md +++ /dev/null @@ -1,430 +0,0 @@ -## 1. Grundidee: Was ist nginx – gerade im Python‑Kontext? - -nginx („engine x“) ist ein **Webserver** und **Reverse Proxy**, der: - -- HTTP(S)-Anfragen aus dem Internet annimmt -- statische Dateien (HTML, CSS, Bilder, JS) direkt ausliefert -- Anfragen für **dynamische Inhalte** (z.B. deine Python‑App) an einen **Anwendungsserver** weiterleitet (z.B. Gunicorn, uWSGI, [[uvicorn]]) - -Im Python‑Kontext wird nginx fast immer als **„Vorderseite“ (Frontend) vor einer Python‑Webanwendung** eingesetzt. - -Typischer Aufbau: - -```text -Browser des Nutzers -> nginx -> Gunicorn/uWSGI/uvicorn -> Python-App (Django, Flask, FastAPI, ...) - (Port 80/443) (lokaler Port, z.B. 8000) -``` - - ---- - -## 2. Einordnung in die Webwelt: Grundbegriffe - -Damit nginx einzuordnen ist, müssen ein paar Begriffe klar sein: - -### 2.1 Webserver - -Ein **Webserver**: - -- läuft auf einem Rechner (Server) -- „lauscht“ auf Port 80 (HTTP) oder 443 (HTTPS) -- nimmt HTTP-Anfragen entgegen und schickt HTTP-Antworten zurück - -Beispiele: -- **nginx** -- **Apache HTTP Server** -- (leichtere) Caddy, lighttpd - -### 2.2 Application Server / WSGI-/ASGI-Server - -Ein **Application Server** führt deine **Web-App** aus, also deinen Python-Code. - -- Für klassische synchronen Python-Webapps: **WSGI**-Server - - z.B. **[[Gunicorn]]**, **uWSGI**, mod_wsgi -- Für moderne, asynchrone Apps: **ASGI**-Server - - z.B. **[[uvicorn]]**, **[[hypercorn]]**, Daphne - -Der App-Server: - -- spricht mit deinem Python-Framework (Django, Flask, [[FastAPI]], etc.) -- versteht Anfragen im WSGI/ASGI-Format -- erzeugt dynamische Antworten (z.B. JSON, HTML aus Templates) - -### 2.3 Python-Webframework (Django, Flask, [[FastAPI]] …) - -Ein **Framework** ist deine Programmierbibliothek, mit der du die Web-App baust: - -- Django: „Vollausstattung“ (ORM, Admin, Auth, etc.) -- Flask: minimaler Kern, viel per Erweiterung -- [[FastAPI]]: modern, async, stark bei APIs - -**Das Framework ist nicht der Webserver.** -Es definiert nur, wie dein Code auf Anfragen reagieren soll. - ---- - -## 3. Abgrenzung: nginx vs. verwandte Begriffe - -### 3.1 nginx vs. Apache - -Beide sind **Webserver**, Unterschiede grob: - -- **nginx** - - schnell bei vielen gleichzeitigen Verbindungen - - ressourcensparend (eventbasiert) - - sehr gut als Reverse Proxy, Load Balancer, TLS-Terminator -- **Apache** - - älter, sehr weit verbreitet - - modulbasiert, viel Legacy - - kann PHP & Co. direkt ausführen (mod_php, etc.) - -Im modernen Python-Umfeld: **nginx + Gunicorn/[[uvicorn]]** ist ein sehr gängiges Setup. - -### 3.2 nginx vs. [[Gunicorn]]/uWSGI/[[uvicorn]] - -- **nginx**: Webserver / Reverse Proxy -- **[[Gunicorn]]/uWSGI/[[uvicorn]]**: Python-Application-Server - -nginx: - -- kümmert sich um HTTP(S), TLS, statische Dateien, Load Balancing -- schickt Anfragen an den Application-Server weiter - -[[Gunicorn]]/[[uvicorn]]: - -- startet deine Python-App -- verarbeitet die Anfragen logikseitig -- rendert Templates, greift auf Datenbanken zu, etc. - -### 3.3 nginx vs. Django/Flask/[[FastAPI]] - -- nginx: Infrastruktur-Komponente (Server) -- Django/Flask/[[FastAPI]]: Bibliotheken, mit denen du deinen Code schreibst - -Eine typische Kombi: - -```text -nginx + Gunicorn + Django -nginx + uvicorn + FastAPI -nginx + uWSGI + Flask -``` - -Jede Komponente hat ihren klaren Aufgabenbereich. - - ---- - -## 4. Welche Probleme löst nginx im Python-Setup? - -### 4.1 Sichere und stabile Auslieferung deiner Python-Webapp - -Problem: -Die eingebaute Entwicklungsserver in Django/Flask sind nur für **lokale Entwicklung** gedacht: - -- wenig performant -- kaum Sicherheitsfeatures -- nicht für viele gleichzeitige Nutzer ausgelegt - -Lösung: -nginx + App-Server (Gunicorn/uvicorn): - -- nginx nimmt alle Anfragen entgegen -- Python-App läuft hinter nginx auf einem internen Port -- Anwendung ist stabiler, sicherer, skalierbarer - -### 4.2 Auslieferung statischer Dateien - -Problem: -Python-Frameworks sind nicht optimiert, um Millionen von statischen Dateien schnell auszuliefern. - -Lösung: - -- nginx kann statische Dateien extrem effizient ausliefern -- Python-App wird entlastet - -**Beispiel:** Django-Setup: - -```text -Browser -> nginx -> (statische Dateien: /static/) direkt von nginx - -> (dynamische URLs: /api/, /admin/) an Gunicorn/Django -``` - -### 4.3 HTTPS / TLS-Termination - -Problem: -Du willst deine Seite unter **https://deinedomain.de** mit Zertifikat ausliefern. -Python-Application-Server kümmern sich eher nicht um Zertifikate. - -Lösung: - -- nginx bietet HTTPS/TLS an (inkl. Let’s Encrypt-Integration) -- entschlüsselt eingehende Anfragen -- leitet sie dann intern als HTTP an Gunicorn/uvicorn weiter - -So muss sich deine Python-App nicht um Zertifikate kümmern. - -### 4.4 Load Balancing (Lastverteilung) - -Problem: -Eine einzelne Instanz deiner Python-App reicht nicht bei hoher Last. - -Lösung: - -- nginx kann Anfragen auf mehrere App-Server verteilen: - -```text -Browser -> nginx -> Gunicorn Instanz 1 - -> Gunicorn Instanz 2 - -> Gunicorn Instanz 3 -``` - -Damit erreichst du höhere Verfügbarkeit und Kapazität. - -### 4.5 Reverse Proxy / Schutz der App - -Problem: -Du willst deine App nicht direkt ins Internet hängen (Sicherheitsrisiko, private Ports). - -Lösung: - -- nginx steht „davor“ und leitet nur definierte Anfragen durch -- interne Ports (z.B. 8000) sind von außen nicht erreichbar -- nginx kann zusätzliche Security-Header, Ratelimits, IP-Filter etc. einsetzen - - ---- - -## 5. Herausforderungen bei der Arbeit mit nginx - -### 5.1 Konfigurationssyntax & -struktur - -- nginx wird über Textdateien konfiguriert (z.B. `/etc/nginx/nginx.conf` und `sites-available/...`) -- die Syntax ist am Anfang ungewohnt: - - `http`, `server`, `location`, `upstream`, `listen`, etc. -- Fehler in der Konfiguration führen dazu, dass nginx nicht startet oder falsch weiterleitet - -Praxis-Hinweise: - -```bash -sudo nginx -t # Konfiguration testen -sudo systemctl reload nginx # Konfiguration neu laden -``` - -### 5.2 Rechte & Pfade (Linux-Dateisystem) - -Probleme: - -- nginx läuft als bestimmter Benutzer (z.B. `www-data`) -- Python-App läuft evtl. als anderer Benutzer (z.B. `myapp`) -- Zugriffsrechte auf Dateien/Ordner (Logs, statische Dateien) müssen passen - -Beispiel: -Wenn statische Dateien unter `/var/www/myapp/static/` liegen, muss der nginx-User Lesezugriff haben. - -### 5.3 Fehlersuche (Debugging) - -- Fehler können an mehreren Stellen auftreten: - - DNS / Domain falsch - - nginx-Konfiguration fehlerhaft - - App-Server (Gunicorn/uvicorn) läuft nicht - - Python-App wirft Fehler - -Praxis-Tipp: - -- in nginx-Logs schauen: - - `/var/log/nginx/access.log` - - `/var/log/nginx/error.log` -- in App-Logs schauen -- Ports prüfen: `ss -tulpen` oder `netstat -tulpen` - -### 5.4 Performance-Tuning - -Fortgeschrittener, aber wichtig bei größerem Traffic: - -- wie viele Worker-Prozesse nginx nutzt -- Keep-Alive-Einstellungen -- Buffergrößen -- Cache-Konfiguration für statische Inhalte - -Am Anfang reicht meist die Standardkonfiguration, später optimiert man bei Bedarf. - - ---- - -## 6. Praxisnahe Beispiele - -### 6.1 Minimalbeispiel: Flask-App hinter nginx + Gunicorn - -**1. Eine einfache Flask-App (Python)** - -Datei: `app.py` - -```python -from flask import Flask - -app = Flask(__name__) - -@app.route("/") -def index(): - return "Hallo, Welt! Das kommt aus Flask hinter nginx." -``` - -Start des App-Servers (Gunicorn): - -```bash -pip install flask gunicorn -gunicorn -w 3 -b 127.0.0.1:8000 app:app -# -w 3: 3 Worker -# -b 127.0.0.1:8000: lauscht auf Port 8000 nur lokal -``` - -**2. nginx-Konfiguration (vereinfacht)** - -Datei: z.B. `/etc/nginx/sites-available/myflaskapp`: - -```nginx -server { - listen 80; - server_name example.com; # deine Domain oder IP - - # Statische Dateien (wenn du welche hast) - # root /var/www/myflaskapp; # falls du HTML/Assets direkt ablegen willst - - location / { - proxy_pass http://127.0.0.1:8000; # Gunicorn - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Dann: - -```bash -sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl reload nginx -``` - -Ablauf: - -1. Browser ruft `http://example.com/` auf -2. nginx nimmt Anfrage auf Port 80 entgegen -3. nginx leitet an `http://127.0.0.1:8000` weiter (Gunicorn/Flask) -4. Flask antwortet mit „Hallo, Welt“ -5. nginx sendet diese Antwort an den Browser zurück - - -### 6.2 Beispiel: Django mit statischen Dateien - -**1. Django-Setup (stark vereinfacht)** - -In `settings.py`: - -```python -STATIC_URL = "/static/" -STATIC_ROOT = "/var/www/mydjangoproject/static/" -``` - -Danach statische Dateien sammeln: - -```bash -python manage.py collectstatic -``` - -**2. nginx-Konfiguration (Ausschnitt)** - -```nginx -server { - listen 80; - server_name example.com; - - # Statische Dateien direkt von nginx - location /static/ { - alias /var/www/mydjangoproject/static/; - access_log off; - expires 30d; - } - - # Alles andere an Django via Gunicorn - location / { - proxy_pass http://127.0.0.1:8001; # hier läuft Gunicorn - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Vorteil: - -- statische Inhalte kommen extrem schnell von nginx -- Django/Gunicorn verarbeitet nur „echte“ App-Logik - - -### 6.3 Beispiel: HTTPS mit Let’s Encrypt (Konzept) - -Mit Tools wie **certbot** kannst du für nginx kostenlos TLS-Zertifikate holen. - -Grobe Schritte: - -```bash -sudo apt install certbot python3-certbot-nginx -sudo certbot --nginx -d example.com -``` - -certbot: - -- holt Zertifikat -- passt nginx-Konfiguration an (Port 443, SSL-Parameter) -- richtet automatische Erneuerung ein - -nginx-Konfiguration enthält danach z.B.: - -```nginx -server { - listen 443 ssl; - server_name example.com; - - ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; - - location / { - proxy_pass http://127.0.0.1:8000; - ... - } -} -``` - -Damit ist deine Python-App per HTTPS erreichbar. - ---- - -## 7. Zusammenfassung - -- **nginx** ist ein leistungsfähiger Webserver und Reverse Proxy, der im Python-Kontext typischerweise: - - vor deiner Python-Webanwendung sitzt - - HTTP(S) abwickelt - - statische Dateien effizient ausliefert - - Anfragen an einen Python-Application-Server (Gunicorn, uWSGI, uvicorn) weiterleitet - - TLS/HTTPS, Load Balancing, Security-Features bereitstellt - -- **Abgrenzung**: - - nginx ≠ Python-Framework (Django/Flask/[[FastAPI]]) - - nginx ≠ Application-Server (Gunicorn/uvicorn) - - nginx = Webserver/Reverse Proxy - -- **Gelöste Probleme**: - - sichere Produktion anstelle von Entwicklungsservern - - Performance für statische Inhalte - - TLS/HTTPS, Lastverteilung, Schutz der App - -- **Herausforderungen**: - - Konfigurationsdateien verstehen - - Rechte & Pfade korrekt setzen - - Fehler systematisch debuggen - - bei hoher Last Performance tunen - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/OAuth2.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/OAuth2.md deleted file mode 100755 index eaa1b61..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/OAuth2.md +++ /dev/null @@ -1,360 +0,0 @@ -Im Folgenden bekommst du eine Einführung in OAuth 2, so dass du danach: - -- weißt, was OAuth 2 grundsätzlich ist, -- es von verwandten Konzepten wie „Login mit Google“, OpenID Connect oder reiner Authentifizierung unterscheiden kannst, -- verstehst, welche Probleme OAuth 2 löst – und welche neuen Herausforderungen es mit sich bringt, -- und ein paar praxisnahe Szenarien im Kopf hast. - ---- - -## 1. Grundsätzliche Definition: Was ist OAuth 2? - -**Kurze Definition**: -OAuth 2 ist ein *Autorisierungs*-Framework. Es regelt, wie eine Anwendung (Client) **im Namen eines Benutzers** sicher auf Ressourcen (z. B. APIs, Daten) zugreifen kann, **ohne dass die Anwendung das Passwort des Benutzers kennen muss**. - -Es geht also primär um: - -- **Zugriffsrechte (Authorization)** -- **Delegation**: „Ich (Nutzer) erlaube App X, auf meine Daten bei Dienst Y zuzugreifen.“ - -### Zentrales Prinzip - -Statt: -- Benutzer gibt sein Passwort direkt bei jeder Drittanbieter-App ein - -nutzt man: -- Benutzer loggt sich beim **vertrauenswürdigen Dienst** ein (z. B. Google, GitHub, dein Unternehmens-Identity-Server) -- Dieser Dienst stellt ein **Token** aus (eine Art Eintrittskarte) -- Drittanbieter-App nutzt dieses Token, um beim API-Server Zugriff zu bekommen - -Dadurch muss die Drittanbieter-App nie das Passwort sehen – sie bekommt **nur einen begrenzten, kontrollierten Zugriff**. - ---- - -## 2. Die wichtigsten Rollen in OAuth 2 - -Um Beispiele zu verstehen, braucht man die Standardrollen: - -1. **Resource Owner** - Der Besitzer der Daten/Ressourcen – meist der **Benutzer**. - -2. **Client** - Die Anwendung, die im Namen des Benutzers zugreifen möchte - (z. B. eine mobile App, eine Web-App, ein Backend-Service). - -3. **Resource Server** - Die API/das System, das die geschützten Ressourcen bereitstellt - (z. B. Google Calendar API, GitHub API, Unternehmens-REST-API). - -4. **Authorization Server** - Der Server, der: - - den Benutzer authentifiziert (z. B. Login-Formular) und - - **Tokens** ausstellt (Access Tokens, optional Refresh Tokens). - -Oft sind Authorization Server und Resource Server derselbe technische Dienst, aber **konzeptionell** werden sie getrennt betrachtet. - ---- - -## 3. Grund-Mechanik: Wie läuft ein OAuth-2-Flow ab? - -Stark vereinfacht am typischen Web-Szenario: - -1. Benutzer nutzt eine Drittanbieter-App (Client) und klickt z. B. auf: - „Verbinde dich mit meinem Google-Kalender“. - -2. Client leitet den Browser zum **Authorization Server** (z. B. accounts.google.com) weiter: - - mit Angaben wie: „Diese App möchte Zugriff auf Kalenderdaten“. - -3. Benutzer loggt sich **beim Authorization Server** ein (falls nicht schon eingeloggt). - -4. Benutzer bekommt eine Seite angezeigt: - „Die Anwendung X möchte auf Ihre Kalenderdaten zugreifen. Erlauben? (Ja/Nein)“ - -5. Wenn Benutzer zustimmt: - - Authorization Server schickt den Benutzer zurück zur App - - Die App bekommt einen **Authorization Code** (bei einem gängigen Flow). - -6. Die App tauscht diesen Code im Hintergrund beim Authorization Server gegen ein **Access Token** (und evtl. ein **Refresh Token**) ein. - -7. Die App nutzt das Access Token, um beim **Resource Server** (z. B. Google Calendar API) Daten abzufragen. - -Der Benutzer muss sein **Passwort nur beim Authorization Server** eingeben – nicht bei der Drittanbieter-App. - ---- - -## 4. Abgrenzung zu ähnlichen / verwandten Begriffen - -### 4.1 OAuth 2 (Authorization) vs. Authentifizierung (Login) - -**Authentifizierung**: „Wer bist du?“ -**Autorisierung**: „Worauf darfst du zugreifen?“ - -- OAuth 2 löst formal **Autorisierung** (Delegation von Zugriffsrechten). -- Viele Systeme nutzen OAuth 2 aber **im Zusammenhang mit Login**, was verwirrend sein kann. - -Ein Login mit Google, bei dem eine Anwendung am Ende weiß „dieser User ist Max Mustermann“, basiert meist auf **OpenID Connect**, das **auf OAuth 2 aufsetzt**. - -### 4.2 OAuth 2 vs. OpenID Connect (OIDC) - -- **OAuth 2**: Spezifiziert, wie Clients **Access Tokens** bekommen, um auf APIs zuzugreifen. -- **OpenID Connect**: Erweiterung auf Basis von OAuth 2, um **Identität** zu transportieren. - -OIDC liefert zusätzlich: -- ein **ID Token** (meist ein [[JWT token]]), das Informationen wie: - - User-ID - - E-Mail - - Name - enthält. - -Praxis-Beispiele: - -- „Mit Google anmelden“ (Login) → **OpenID Connect** -- „Google Kalender-API nutzen“ → **OAuth 2** (Autorisierung für API-Zugriff) - -### 4.3 OAuth 2 vs. API-Key - -**API-Key**: -- Meist ein statischer Schlüssel, der im Client eingebaut ist. -- Unterscheidet nicht zwischen verschiedenen Benutzern. -- Hat wenig feingranulare Rechte (oft: „alles oder nichts“). -- Sicherheitsrisiko bei Leaks (z. B. im Frontend-Code). - -**OAuth 2**: -- Nutzerbezogen (Access Token repräsentiert den Benutzer/das Delegationsrecht). -- Token haben meist begrenzte Lebensdauer. -- Zugriffsrechte (Scopes) sind feiner steuerbar. - -### 4.4 OAuth 2 vs. SAML - -- **SAML**: Älterer Standard, XML-basiert, häufig in Unternehmensumgebungen für SSO verwendet. -- **OAuth 2 / OIDC**: JSON/HTTP-basiert, leichtergewichtig, stark im Web- und Mobile-Bereich verbreitet. - -Grober Merksatz: -- SAML: klassische Unternehmen, Single Sign-On im Browser, ältere Infrastruktur. -- OAuth 2 + OIDC: moderne Web-/Mobile-Apps, REST-APIs, Microservices. - ---- - -## 5. Welche Probleme löst OAuth 2? - -### 5.1 Vermeidung von Passwortweitergabe an Drittanbieter - -Früher: -Eine App wollte auf Gmail zugreifen => Benutzer musste **sein Gmail-Passwort** in dieser App eingeben. -Risiken: -- App könnte Passwort speichern oder missbrauchen. -- Bei Passwortänderung müsste der Benutzer alles neu einrichten. -- Anbieter (Google) hat keine Kontrolle, welche App das tut. - -Mit OAuth 2: -- Benutzer loggt sich nur bei Google ein. -- Drittanbieter-App erhält nur ein **Token mit begrenzten Rechten**, kein Passwort. - -**Praxisbeispiel**: -Eine To-Do-App möchte deine Google Kalender-Einträge anzeigen: -Du klickst „Mit Google verbinden“, wirst zu Google geleitet, bestätigst die Berechtigungen und die App sieht danach deine Kalender – ohne dein Passwort zu kennen. - -### 5.2 Feingranulare Zugriffsrechte (Scopes) - -Mit **Scopes** kann eingeschränkt werden, worauf eine App zugreifen darf. - -Beispiele: -- `read_calendar`: nur Leserechte auf Kalender -- `write_calendar`: Schreibrechte auf Kalender -- `email`: Zugriff auf deine E-Mail-Adresse -- `profile`: Zugriff auf deine Basisprofildaten - -So kann der Benutzer (und auch der Resource Server) klar kontrollieren: -- Eine App darf z. B. **nur lesen**, nicht schreiben. -- Eine App darf **nur auf E-Mail und Profil**, nicht auf Kontakte oder Drive zugreifen. - -### 5.3 Zeitlich begrenzte Tokens - -**Access Tokens** sind typischerweise kurzlebig (z. B. 5–60 Minuten). - -- Wenn ein Token geleakt wird, ist der Schaden zeitlich begrenzt. -- Über Revocation-Mechanismen kann der Zugriff vorzeitig beendet werden. - -Optional gibt es **Refresh Tokens**: -- Länger gültig -- Dienen dazu, neue Access Tokens zu holen -- Können vom Authorization Server gezielt deaktiviert werden (z. B. bei Kompromittierung). - -### 5.4 Single Sign-On / bessere User Experience (in Kombination mit OIDC) - -In Kombination mit OIDC und Single-Sign-On-Konzepten: -- Benutzer loggt sich einmal beim zentralen Identity Provider ein. -- Mehrere Anwendungen können dann in einer Session auf Tokens zugreifen, ohne den Benutzer jedes Mal neu einzuloggen. - ---- - -## 6. Typische OAuth-2-Flows (vereinfacht) - -Es gibt verschiedene **„Flows“**, je nach Art des Clients und Nutzungsszenario. Die wichtigsten: - -### 6.1 Authorization Code Flow (heute mit PKCE) - -Typisch für: -- Web-Anwendungen mit Backend -- Single Page Applications (mit PKCE) -- Mobile Apps - -Ablauf (vereinfacht, ohne technische Details): - -1. Anwendung leitet Benutzer zum Authorization Server. -2. Benutzer loggt sich ein und stimmt zu. -3. Authorization Server schickt **Authorization Code** an die Anwendung (über Redirect). -4. Anwendung tauscht Code gegen ein **Access Token** (und optional Refresh Token). - -**PKCE** (Proof Key for Code Exchange) ist eine Sicherheits-Erweiterung, um Code-Diebstahl zu verhindern, insbesondere bei mobilen/Browser-basierten Apps. - -### 6.2 Client Credentials Flow - -Für: -- Machine-to-Machine-Kommunikation (kein Benutzer) -- Hintergrundprozesse, Microservices, Server-zu-Server - -Hier gibt es keinen Benutzer. -Der Client identifiziert sich selbst (z. B. mit Client ID + Client Secret) beim Authorization Server und bekommt ein Access Token, um auf eine API zuzugreifen. - -**Beispiel**: -Ein Backend-Service ruft periodisch eine interne Unternehmens-API auf, um Daten zu synchronisieren. - -### 6.3 Device Code Flow - -Für Geräte ohne vollwertigen Browser oder ohne komfortable Eingabemöglichkeit, wie: -- Smart-TVs -- Konsolen -- IoT-Geräte - -Ablauf: -- Gerät zeigt einen Code und eine URL an (z. B. „Bitte gehen Sie auf https://example.com/device und geben Sie Code ABCD ein“). -- Benutzer geht an seinem Smartphone/PC auf diese URL, loggt sich dort ein und bestätigt. -- Gerät bekommt anschließend ein Token und kann auf die Ressource zugreifen. - ---- - -## 7. Herausforderungen und typische Stolperfallen - -### 7.1 OAuth 2 ist komplex und flexibel - -OAuth 2 ist sehr flexibel – das ist Stärke und Schwäche zugleich: - -- Viele Optionen & Erweiterungen → schnell unübersichtlich. -- Viele Entscheidungen zu treffen: Flows, Token-Formate, Scopes, Lifetimes, PKCE, etc. - -Für Einsteiger wirkt es oft „überdimensioniert“, vor allem wenn man nur „einen einfachen Login“ braucht (wo eigentlich OIDC gefragt ist). - -### 7.2 Verwechslung von OAuth 2 und Login - -Weil viele große Anbieter beides kombiniert haben („Login mit Google“), entsteht oft: - -- „OAuth 2 ist ein Login-Protokoll.“ → **Formal falsch**, es ist ein Autorisierungsframework. -- Für „Login“ verwendet man **OpenID Connect**, auch wenn technisch OAuth-2-Mechanismen verwendet werden. - -Konsequenz: -Fehlkonzeptionen in Projekten, z. B. versucht man reine OAuth-2-Tokens zu nutzen, um Identität abzuleiten, statt OIDC zu verwenden. - -### 7.3 Sicherheitsrisiken bei falscher Implementierung - -Einige typische Fehler: - -- **Tokens in URLs weitergeben** → landen in Logs, Browser-History, Referrer-Headern. -- **Implicit Flow** für Single-Page-Apps ohne ausreichende Sicherheitsmaßnahmen - → gilt inzwischen als überholt; empfohlen wird Authorization Code Flow mit PKCE. -- **Keine Einschränkung der Redirect-URIs** → Angreifer können Redirect-URIs manipulieren und Tokens abfangen. -- **Zu lange gültige Tokens** → höheres Risiko bei Leaks. -- **Speichern von Tokens im unsicheren Speicher** (z. B. localStorage in Browsern) → anfällig für XSS. - -### 7.4 Komplexität für Endnutzer - -Auch für Endnutzer ist die Zustimmung manchmal nicht klar: - -- „Diese App möchte auf Ihr Profil, Ihre Kontakte, Ihre Dateien, Ihre E-Mails zugreifen.“ - → Viele klicken aus Gewohnheit auf „Zulassen“, ohne zu verstehen, was das bedeutet. - -Das ist eher ein UX-/Privacy-Thema, aber eng mit OAuth 2 verbunden, da die Darstellung der **Scopes** oft technisch und verwirrend ist. - ---- - -## 8. Praxisnahe Beispiele - -### Beispiel 1: „Mit Google anmelden“ in einer Web-App - -**Situation**: -Du entwickelst eine Web-App, in der sich Nutzer anmelden können. Statt ein eigenes Benutzerkontensystem aufzubauen, willst du „Login mit Google“ anbieten. - -**In Wahrheit passiert (typisch)**: -- Die App nutzt **OpenID Connect**: - - OAuth 2 regelt den Token-Flow. - - OIDC liefert ein ID Token mit Infos über den Benutzer. -- Nutzer sieht: Google-Login, eine Consent-Seite, und wird danach in deine App zurückgeleitet. -- Deine App bekommt: - - ein **ID Token** (zur Identifikation des Nutzers) - - ein **Access Token** (um z. B. zusätzlich auf Google APIs zuzugreifen, falls gewünscht). - -**OAuth-2-Aspekt**: -Der eigentliche Mechanismus, wie Access Token ausgestellt werden, ist OAuth 2. - -### Beispiel 2: Drittanbieter-Tool liest GitHub-Repositories - -**Situation**: -Ein Build-Tool oder eine CI/CD-Plattform möchte deine privaten GitHub-Repositories lesen, um Builds auszulösen. - -Ablauf: -1. In der CI/CD-Plattform klickst du „Mit GitHub verbinden“. -2. Du wirst zu GitHub weitergeleitet, loggst dich ein. -3. GitHub zeigt dir eine Seite: - „Tool X möchte Zugriff auf: - - Ihre öffentlichen und privaten Repositories - - Ihre Webhooks - …“ -4. Du erlaubst. -5. Die CI/CD-Plattform erhält ein Access Token mit bestimmten **Scopes** (z. B. `repo`). - -**OAuth-2-Aspekt**: -- Das Access Token erlaubt der CI/CD-Plattform, im Namen deines GitHub-Accounts zu agieren, innerhalb des erlaubten Scopes. - -### Beispiel 3: Unternehmensinternes Microservice-System - -**Situation**: -In einer Microservice-Architektur gibt es: -- einen zentralen Authorization Server (z. B. Keycloak, Auth0, Azure AD), -- viele Services, die interne APIs bereitstellen. - -Szenario: -- Ein Frontend bekommt vom Authorization Server (über einen Login) ein Access Token. -- Dieses Access Token wird vom Frontend bei Aufrufen an das API-Gateway oder direkt an Microservices mitgeschickt. -- Microservices verifizieren das Token und entscheiden anhand von Scopes/Rollen, ob der Zugriff erlaubt ist. - -**OAuth-2-Aspekt**: -- Zugriffskontrolle wird zentralisiert. -- Jeder Service muss nicht mehr selbst Benutzer verwalten, sondern nur Access Tokens prüfen. - ---- - -## 9. Zusammenfassung in Stichpunkten - -- **OAuth 2** ist ein **Autorisierungs-Framework** (nicht direkt ein Login-Protokoll). -- Es ermöglicht, dass **Drittanbieter-Apps im Namen eines Nutzers** auf Ressourcen zugreifen können, ohne das Passwort des Nutzers zu kennen. -- Wichtige Rollen: **Resource Owner**, **Client**, **Resource Server**, **Authorization Server**. -- Es arbeitet mit **Tokens** (Access Tokens, optional Refresh Tokens) und **Scopes** (feingranulare Rechte). -- Verwandte Standards: - - **OpenID Connect**: für Authentifizierung/Identität (baut auf OAuth 2 auf). - - **SAML**: alternative, ältere Lösung v. a. für SSO in Unternehmen. -- Es löst Probleme wie: - - Passwortweitergabe an Drittanbieter - - Fehlende Feingranularität von Zugriffsrechten - - Mangelnde Kontrolle über Zugriffsdelegation -- Herausforderungen: - - Komplexität und viele Optionen - - Verwechslungsgefahr mit Authentifizierung - - Sicherheitsrisiken bei falscher Implementierung - - UX/Verständlichkeit von Zustimmungsdialogen -- Praxis: - - „Mit Google/GitHub/Facebook anmelden“ - - Drittanbieter-Apps, die auf APIs zugreifen - - Microservice-Systeme mit zentralem Identity & Access Management - ---- - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Terraform.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Terraform.md deleted file mode 100755 index ac0664e..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/Terraform.md +++ /dev/null @@ -1,530 +0,0 @@ -#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. - ---- - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/kubectl.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/kubectl.md deleted file mode 100755 index 86b4997..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedoens/kubectl.md +++ /dev/null @@ -1,358 +0,0 @@ -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 [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. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Argo CD.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Argo CD.md deleted file mode 100755 index 4ab950d..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Argo CD.md +++ /dev/null @@ -1,358 +0,0 @@ -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 ` -- 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 - - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Ingress.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Ingress.md deleted file mode 100755 index 2d16f30..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Ingress.md +++ /dev/null @@ -1,255 +0,0 @@ -Im IT-Kontext bedeutet **Ingress** allgemein: -> „Eingehender Datenverkehr in ein System, Netzwerk oder einen Dienst.“ - -Je nach Kontext wird der Begriff etwas unterschiedlich verwendet: -# Allgemein - -### 1. Netzwerk / Security - -In klassischen Netzwerken und Firewalls unterscheidet man: - -- **Ingress-Traffic**: Datenverkehr, der **von außen nach innen** in ein Netzwerk oder System hineinkommt - (z. B. HTTP-Anfragen aus dem Internet an Ihren Webserver). -- **Egress-Traffic**: Datenverkehr, der **von innen nach außen** geht - (z. B. Ihr Server ruft eine externe API im Internet auf). - -Typische Themen rund um Ingress in diesem Kontext: -- **Ingress-Filterung**: Regeln, die festlegen, welcher eingehende Traffic erlaubt ist (z. B. Firewall-Regeln). -- **Ingress-Schutz**: Maßnahmen gegen Angriffe von außen (DDoS-Schutz, WAF, Rate Limiting). - -### 2. Kubernetes (Spezialfall „Ingress“ Resource) - -In Kubernetes ist **Ingress** ein eigener Begriff und bezeichnet eine **API-Ressource**, die regelt, wie externe Requests in den Cluster hinein zu den internen Services geleitet werden. - -Kurz gesagt: -- Ein **Ingress** ist eine Art **Reverse Proxy / Routing-Regel** auf Layer 7 (HTTP/HTTPS). -- Er definiert z. B.: - - Welche Domain (`host`) auf welchen Service zeigt - - Welche Pfade (`/api`, `/app`) zu welchen Services geroutet werden - - SSL/TLS-Termination (HTTPS) - -Beispielhafte Ingress-Definition in Kubernetes: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-app-ingress -spec: - rules: - - host: myapp.example.com - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: my-api-service - port: - number: 80 -``` - -Hier sorgt der Ingress dafür, dass Anfragen an `https://myapp.example.com/api` in den Cluster gehen und beim Service `my-api-service` landen. - ---- - -Im Kubernetes-Kontext ist „Ingress“ etwas spezieller als nur „eingehender Traffic“. Es gibt drei eng zusammenhängende Dinge: - -1. **Ingress-Ressource** (YAML-Objekt, das Routing-Regeln beschreibt) -2. **Ingress-Controller** (Software/Pod, der diese Regeln umsetzt) -3. **(Optional) IngressClass** (welcher Controller für welches Ingress zuständig ist) - -Ich gehe der Reihe nach durch. - ---- -# Kubernetes -## 1. Warum braucht man Ingress überhaupt? - -Ohne Ingress hast du im Wesentlichen drei Möglichkeiten, Dienste nach außen verfügbar zu machen: - -- `ClusterIP` (Standard): nur intern im Cluster erreichbar -- `NodePort`: öffnet einen Port auf jedem Node; eher low-level / unkomfortabel -- `LoadBalancer`: Cloud-Loadbalancer vor einem Service (oft pro Service ein eigener LB) - -Nachteile: -- Viele externe IPs / Loadbalancer, wenn du viele Services hast -- Keine zentrale Layer‑7-Logik (z. B. Routing per Hostname/URL-Pfad, TLS-Termination) - -**Ingress** löst genau das: - -> Ein (oder wenige) externe Einstiegspunkte, die per Hostname und Pfad auf viele interne Services routen können, inkl. HTTPS/TLS, Auth, Rate-Limiting (je nach Controller). - ---- - -## 2. Ingress-Ressource (das YAML-Objekt) - -Die Ingress-Ressource beschreibt **Deklarativ**, wie Anfragen von außen zu Services im Cluster geroutet werden sollen: - -- Auf Basis von **Hostname** (`host`, z. B. `api.example.com`) -- Auf Basis von **Pfad** (`path`, z. B. `/api`, `/app`) -- Verknüpft mit einem **Backend-Service** (`Service` + Port) -- Optional: **TLS**-Konfiguration (Zertifikate) - -Beispiel: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-app-ingress - namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / -spec: - ingressClassName: nginx - tls: - - hosts: - - myapp.example.com - secretName: myapp-tls-secret - rules: - - host: myapp.example.com - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: my-api-service - port: - number: 80 - - path: /app - pathType: Prefix - backend: - service: - name: my-frontend-service - port: - number: 80 -``` - -Was hier passiert: - -- Alle Anfragen an `https://myapp.example.com/api...` → Service `my-api-service` -- Alle Anfragen an `https://myapp.example.com/app...` → Service `my-frontend-service` -- TLS-Zertifikat liegt in `Secret myapp-tls-secret` -- `ingressClassName: nginx` sagt: „Dieser Ingress ist für den Ingress-Controller mit Klasse `nginx` gedacht“ - -Wichtige Felder: - -- `rules[].host`: auf welchen Host gilt die Regel? -- `rules[].http.paths[].path`: URL-Pfad -- `pathType`: - - `Prefix` → „beginnt mit diesem Pfad“ - - `Exact` → „genau dieser Pfad“ - - (früher gab es noch `ImplementationSpecific`) -- `backend.service.name` / `port`: zu welchem Service leiten? - ---- - -## 3. Ingress-Controller (die „Engine“ dahinter) - -Die YAML-Ressource alleine macht noch nichts. Es braucht einen **Ingress-Controller**: - -- Läuft als Pod/Deployment im Cluster -- Beobachtet (watch) Ingress-Ressourcen im API-Server -- Übersetzt die Regeln in eine konkrete Konfiguration (z. B. [[NGINX]], Envoy, Traefik …) -- Lauscht auf einem (oder mehreren) Node-Ports/LoadBalancer-IPs und routet Traffic in die passende Services - -Typische Implementierungen: - -- **[[NGINX]] Ingress Controller** -- **Traefik** -- **HAProxy Ingress** -- **Envoy-basierte** Controller -- Cloud-spezifische: - - GCE/GLBC (GKE HTTP(S) Load Balancing) - - AWS ALB Ingress Controller - - Azure Application Gateway Ingress - -Wichtig: -Du kannst mehrere Ingress-Controller im Cluster haben, z. B. einen für öffentliche, einen für interne Services. - ---- - -## 4. IngressClass (Zuordnung Controller ↔ Ingress) - -Mit `IngressClass` kann man genau definieren, **welcher** Ingress-Controller für welche Ingress-Ressourcen zuständig ist. - -Beispiel IngressClass: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: nginx -spec: - controller: k8s.io/ingress-nginx -``` - -Im Ingress verknüpfst du das mit: - -```yaml -spec: - ingressClassName: nginx - # ... -``` - -Somit: - -- Nur der Ingress-Controller, der sich als `k8s.io/ingress-nginx` registriert, reagiert auf diesen Ingress. -- Andere Controller ignorieren ihn. - -Früher wurde das oft über **Annotations** geregelt (`kubernetes.io/ingress.class`), `ingressClassName` ist der neuere, bevorzugte Weg. - ---- - -## 5. Request-Flow: Wie läuft eine Anfrage technisch? - -Typischer Ablauf (z. B. mit [[NGINX]] Ingress Controller in einer Cloud): - -1. Benutzer ruft im Browser `https://myapp.example.com/api/users` auf. -2. DNS zeigt `myapp.example.com` auf die externe IP eines Cloud-Loadbalancers. -3. Der Cloud-Loadbalancer leitet die Anfrage an einen Node/Port, auf dem der Ingress-Controller erreichbar ist. -4. Der Ingress-Controller (Pod) erhält die HTTP(S)-Anfrage. -5. Er vergleicht Host (`myapp.example.com`) und Pfad (`/api/users`) mit seinen Ingress-Regeln: - - Host passt zu `myapp.example.com` - - Pfad passt zum Prefix `/api` -6. Er leitet die Anfrage intern an den Kubernetes-Service `my-api-service:80` weiter. -7. Kubernetes-Service verteilt die Anfrage per kube-proxy/IPVS/iptables auf einen Pod (`my-api`-Pod). -8. Antwort geht den Weg zurück und landet beim Benutzer. - ---- - -## 6. Typische Features (Controller-abhängig) - -Je nach Ingress-Controller gibt es zusätzliche Möglichkeiten, meist über **Annotations** oder spezifische CRDs: - -- **TLS-Termination** und optional TLS-Passthrough -- **HTTP → HTTPS Redirect** -- **Path-Rewrites**, z. B. `/api` außen → `/` im Backend -- **Authentifizierung** (z. B. OAuth2, Basic Auth) -- **Rate Limiting**, IP-Whitelist/Blacklist -- **Canary-Deployments / Traffic-Splitting** -- **gRPC-Support**, WebSockets, etc. - -Beispiel (nginx Spezifische Annotation für Redirect): - -```yaml -metadata: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" -``` - ---- - -## 7. Grenzen von Ingress & Gateway API - -Die klassische Ingress-API ist bewusst relativ simpel (Host, Pfad, Backend, TLS). -Für komplexere Szenarien (multi-tenant, umfangreiche Routing-Logik, mehrere Gateways) gibt es inzwischen die **Gateway API** (Ressourcen wie `Gateway`, `HTTPRoute` etc.), die Ingress langfristig ergänzen/teilweise ablösen soll. - -Dennoch ist Ingress aktuell (2024) in vielen Clustern der Standardweg, HTTP/HTTPS-Traffic von außen hereinzubringen. - ---- - -Wenn du möchtest, kann ich als Nächstes: - -- ein konkretes Setup „von Null an“ durchgehen (z. B. Minikube + [[NGINX]] Ingress + Beispiel-App), oder -- spezifisch auf Annotations/Features eines bestimmten Ingress-Controllers eingehen (etwa nginx oder Traefik). \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/JWT token.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/JWT token.md deleted file mode 100755 index a1f465a..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/JWT token.md +++ /dev/null @@ -1,427 +0,0 @@ -Ein JSON Web Token (JWT) wirkt am Anfang oft etwas „magisch“. Wenn man es einmal verstanden hat, ist es aber ein relativ simples Baustein-konzept für Authentifizierung und Autorisierung in modernen Web‑ und API‑Systemen. - -Ich gehe Schritt für Schritt durch: - -1. Grundidee: Was ist ein JWT? -2. Aufbau eines JWT -3. Abgrenzung zu verwandten Konzepten -4. Welche Probleme werden mit JWT gelöst? -5. Typische Anwendungsfälle (praxisnah erklärt) -6. Herausforderungen und Fallstricke -7. Kurz-Fazit - ---- - -## 1. Grundidee: Was ist ein JWT? - -**Definition (vereinfacht):** - -Ein **JWT (JSON Web Token)** ist ein **kompakter, URL-tauglicher, digital signierter Datenblock**, der Informationen über einen Benutzer oder eine Aktion enthält (z.B. „Benutzer X ist eingeloggt und hat Rolle admin“). -Diese Informationen sind im JSON-Format strukturiert und werden mit einem kryptografischen Verfahren signiert (und optional verschlüsselt). - -Damit kann ein Server (oder mehrere Server) **prüfen**, ob das Token: -- von einem vertrauenswürdigen Aussteller stammt, -- nicht manipuliert wurde, -- noch gültig ist (nicht abgelaufen). - -Man kann sich einen JWT grob als „fälschungssichereren Ausweis“ für Benutzer (oder Clients) vorstellen, der vom Server ausgestellt wird und den der Client bei weiteren Anfragen vorzeigt. - ---- - -## 2. Aufbau eines JWT - -Ein JWT besteht aus **drei Teilen**, jeweils Base64URL-kodiert, durch Punkte getrennt: - -```text -HEADER.PAYLOAD.SIGNATURE -``` - -Ein Beispiel (gekürzt, sieht in der Praxis ähnlich aus): - -```text -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 -. -eyJzdWIiOiIxMjM0NTYiLCJlbWFpbCI6Im1heC5tdXN0ZXJAbWFpbC5jb20iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MzAxNjkzNDN9 -. -dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk -``` - -### 2.1 Header - -Der Header beschreibt u.a.: -- welches Signaturverfahren genutzt wird, -- dass es sich um ein JWT handelt. - -Beispiel: - -```json -{ - "alg": "HS256", // Algorithmus: HMAC-SHA256 - "typ": "JWT" -} -``` - -### 2.2 Payload (Claims) - -Die Payload enthält die eigentlichen Informationen („Claims“). -Beispiele: - -```json -{ - "sub": "123456", // subject: Benutzer-ID - "email": "max.mustermann@example.com", - "role": "admin", - "iat": 1730169334, // issued at (Unix-Timestamp) - "exp": 1730172934, // expiry: Ablaufzeitpunkt - "iss": "https://auth.meine-app.de", // issuer - "aud": "https://api.meine-app.de" // audience -} -``` - -Wichtige Standard-Claims (nicht verpflichtend, aber üblich): -- `iss` – Issuer (Wer hat das Token ausgestellt?) -- `sub` – Subject (Auf wen bezieht sich das Token? z.B. Benutzer-ID) -- `aud` – Audience (Für welche Anwendung/Services ist das Token bestimmt?) -- `exp` – Expiration Time (Wann läuft das Token ab?) -- `iat` – Issued At (Wann wurde es ausgestellt?) -- `nbf` – Not Before (Ab wann ist es gültig?) - -Dazu kommen **anwendungsspezifische Claims**, z.B.: -- `email`, `username` -- `roles`: `["admin", "user"]` -- `scope`: `"read:orders write:orders"` - -### 2.3 Signature - -Die Signatur wird aus dem Header + Payload + einem Geheimnis (oder Schlüssel) gebildet. -Formel grob: - -```text -signature = Sign( base64Url(header) + "." + base64Url(payload), secret_or_private_key ) -``` - -Beim Prüfen: -- rekonstruiert der Server diese Signatur aus Header+Payload, -- vergleicht sie mit der Signatur im Token, -- wenn sie übereinstimmen → Token wurde nicht manipuliert. - ---- - -## 3. Abgrenzung zu verwandten Begriffen - -### 3.1 JWT vs klassische Session-Cookies - -**Klassische Session:** -- Nach Login erzeugt der Server eine **Session-ID** und speichert alle Infos (Benutzer, Rollen usw.) **im Server-Speicher** (in Memory, Redis, DB). -- Die Session-ID wird im Browser z.B. als Cookie gespeichert. -- Bei jeder Anfrage sendet der Browser die Session-ID; Server schaut im eigenen Speicher nach, welche Daten dazu gehören. - -**JWT:** -- Nach Login erzeugt der Server ein **Token**, das die Infos (Benutzer, Rollen, Ablaufzeit) **direkt im Token** enthält. -- Das Token wird typischerweise im Browser (Cookie oder Speicher der App) gehalten. -- Bei jeder Anfrage schickt der Client das Token mit (z.B. via HTTP-Header `Authorization: Bearer `). -- 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 -``` - -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 `. -- `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= -``` - -4. Benutzer klickt den Link. -5. Frontend schickt den Token an das Backend, z.B. POST `/auth/reset-password`. -6. Backend prüft: - - Signatur, - - `exp` (noch gültig?), - - `purpose == "password_reset"`. -7. Wenn alles passt → Passwort darf geändert werden. - ---- - -## 6. Herausforderungen und Fallstricke - -JWTs lösen einige Probleme, bringen aber eigene Herausforderungen mit sich. - -### 6.1 Sicherheit bei Signatur und Schlüssel - -Wichtige Punkte: - -- **Algorithmuswahl:** - Typisch: - - Symmetrisch: `HS256` (ein gemeinsames Secret für Signatur & Prüfung) - - Asymmetrisch: `RS256` (privater Schlüssel signiert, öffentlicher Schlüssel prüft) -- **Keys sicher verwalten:** - - nicht im Code hardcoden, - - in sicheren Secret-Stores halten, - - Schlüssel regelmäßig rotieren. - -Gefährliche Anti-Pattern: -- `alg: none` akzeptieren (nie tun), -- Server so konfigurieren, dass er das `alg` aus dem Token blind vertraut (statt fest vorzugeben, was erlaubt ist). - -### 6.2 Token-Ablauf und „Logout“ - -JWT ist stateless – das macht „Logout“ tricky: - -- Klassische Session: - - Session im Server-Speicher löschen → Benutzer abgemeldet. -- JWT: - - Token ist selbstständig gültig, bis `exp` erreicht ist. - - Serverseitig gibt es **keine zentrale Liste** aller aktiven JWTs. - -Mögliche Lösungen: -- Tokens **kurzlebig** machen, z.B. 5–15 Minuten. -- Für längere Sessions: - - **Access Token** kurzlebig, - - **Refresh Token** längerlebig und serverseitig verwaltbar (z.B. in DB), - - bei Logout → Refresh Token invalidieren, Access Token läuft automatisch bald ab. -- Optional: Blacklists/Blocklists oder Token-Revocation-Mechanismen. - -### 6.3 Wo speichert man JWT im Browser? - -Varianten: -- **httpOnly Cookie**: - - Vorteil: nicht via JavaScript auslesbar → Schutz gegen XSS. - - Nachteil: CSRF muss beachtet werden (CSRF-Protection implementieren). -- **localStorage / sessionStorage**: - - Vorteil: einfache Handhabung im JS-Code. - - Nachteil: bei XSS kann ein Angreifer das Token auslesen → sehr riskant. - -In der Praxis: -- Oft Kombination aus httpOnly Cookies + CSRF-Schutz, -- oder Speziallösungen mit sog. „Double Submit Cookies“, „SameSite“-Cookies etc. - -### 6.4 Token sind nicht geheim (wenn nur signiert) - -Wichtiger Punkt: -- Ein **normaler signierter JWT ist nicht verschlüsselt**. -- Jeder, der das Token sieht, kann die Payload (Claims) lesen, da sie Base64URL-kodiert ist (was keine Verschlüsselung ist). - -Daraus folgt: -- Keine hochsensiblen Daten in die Payload packen (z.B. Passwörter, Zahlungsdaten, interne Geheimnisse). -- JWT nur über **HTTPS** übertragen, damit niemand auf dem Weg „mithören“ kann. -- Wenn wirklich Geheimhaltung nötig ist → **JWE (verschlüsseltes JWT)** oder andere Verschlüsselungsmechanismen. - -### 6.5 Token-Größe - -Wenn man sehr viele Claims ins Token packt: -- wird es groß, -- verursacht mehr Traffic, -- passt evtl. schlechter in Cookies oder Header. - -Praktisch: -Nur wirklich relevante Informationen ins JWT aufnehmen: -- Benutzer-ID -- Rollen, Rechte oder Scope -- wenige zusätzliche Angaben - -Den Rest kann man bei Bedarf aus der Datenbank abfragen. - -### 6.6 Fehler bei der Prüfung des Tokens - -Beim Validieren darf man nicht nur die Signatur prüfen, sondern auch: - -- Ist das Token abgelaufen? (`exp`) -- Ist das Token noch nicht gültig? (`nbf`, `iat`) -- Stimmt `iss` (Issuer) mit dem erwarteten Aussteller überein? -- Stimmt `aud` (Audience) mit der eigenen Anwendung/API überein? - -Beispiel (Node.js mit `jsonwebtoken`): - -```js -const jwt = require('jsonwebtoken'); - -const token = '...'; // vom Client erhalten -const publicKey = '...'; // Public Key (bei RS256) oder Secret (bei HS256) - -try { - const payload = jwt.verify(token, publicKey, { - algorithms: ['RS256'], - issuer: 'https://auth.meine-app.de', - audience: 'https://api.meine-app.de', - }); - - console.log('Token gültig, Benutzer-ID:', payload.sub); -} catch (err) { - console.error('Ungültiges Token:', err.message); -} -``` - ---- - -## 7. Kurz-Fazit - -- Ein **JWT** ist ein **signierter, kompakter Datenträger im JSON-Format**, der Claims über Benutzer/Clients enthält. -- Er wird verwendet für: - - Authentifizierung (z.B. „Benutzer X ist eingeloggt“), - - Autorisierung (z.B. „Benutzer X hat die Rolle admin“), - - zeitlich begrenzte Aktionen (Passwort-Reset, E-Mail-Bestätigung), - - verteilte Systeme (Microservices), die ohne zentralen Session-Speicher auskommen sollen. -- JWTs sind besonders sinnvoll in: - - Web-APIs, - - SPAs, - - mobilen Apps, - - Microservice- und Cloud-Architekturen. -- Abgrenzung: - - JWT ≠ OAuth2/OIDC (Protokoll vs. Tokenformat), - - JWT ≠ Session-Cookie (zustandslos vs. zustandsbehaftet), - - JWTs sind meist JWS (signiert, aber nicht verschlüsselt). -- Herausforderungen: - - Sichere Schlüsselverwaltung, korrekte Token-Prüfung, - - Umgang mit Logout/Token-Invalidierung, - - sicherer Speicherort im Client (XSS/CSRF), - - kein Hineinpacken hochsensibler Daten. - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md deleted file mode 100755 index 75909b2..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes Endpoints finden und erreichen - Anleitung für Einsteiger.md +++ /dev/null @@ -1,512 +0,0 @@ -## 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 ` - -#### 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 ` - -#### 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 ` - -#### 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 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 -n -``` - -**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 -``` - -**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 -n -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 svc/ : -``` - -**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 -n -``` - -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: - ``` - 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 -n -``` - -2. **Direkt mit curl testen:** -```bash -curl -H "Host: openmetadata.dma.aai-dfine.de" http:// -``` - -**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 ` -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 -n - -# Letzte 100 Zeilen -kubectl logs -n --tail=100 - -# Vorheriger Container (wenn Pod neu gestartet wurde) -kubectl logs -n --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 - -# 2. Sind Endpoints vorhanden? -kubectl get endpoints -n - -# 3. Service-Details -kubectl describe svc -n -``` - ---- - -### Problem: Authentifizierung erforderlich - -**Wenn Login-Seite erscheint:** - -**Standard-Credentials suchen:** -```bash -# Secrets auflisten -kubectl get secrets -n - -# Secret anschauen (Werte sind base64-kodiert) -kubectl get secret -n -o yaml - -# Secret dekodieren -kubectl get secret -n -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 - -# Alle Ingress-Endpoints -kubectl get ingress --all-namespaces -``` - -### Status prüfen -```bash -# Pods mit mehr Details -kubectl get pods -n -o wide - -# Pod-Logs live verfolgen -kubectl logs -f -n - -# In Pod einloggen (für Debugging) -kubectl exec -it -n -- /bin/bash -``` - -### Konfiguration anschauen -```bash -# YAML eines Ingress -kubectl get ingress -n -o yaml - -# Service-Details -kubectl describe svc -n - -# ConfigMap auslesen -kubectl get configmap -n -o yaml -``` - -### Port-Forwarding -```bash -# Standard -kubectl port-forward -n svc/ : - -# Zu einem Pod (falls Service nicht existiert) -kubectl port-forward -n : - -# Alle Interfaces binden (auch im Netzwerk erreichbar) -kubectl port-forward --address 0.0.0.0 -n svc/ 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 - ``` - -3. **Port-Forwarding starten:** - ```bash - kubectl port-forward -n svc/ 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`* diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes.md deleted file mode 100755 index b913b42..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Kubernetes.md +++ /dev/null @@ -1,299 +0,0 @@ -#IT #Container #Open-Source #Cloud - ---- - -## 1. Was ist Kubernetes – in einem Satz? - -Kubernetes ist ein Open-Source‑System zur **Orchestrierung von Containern** (z. B. Docker), das die **Bereitstellung, Skalierung und Verwaltung** von Anwendungen automatisiert. - -- Entwickelt von Google, heute von der CNCF (Cloud Native Computing Foundation) verwaltet -- Läuft in der Cloud (AWS, Azure, GCP), im Rechenzentrum (On‑Prem) oder lokal (z. B. mit kind, minikube) - ---- - -## 2. Warum braucht man Kubernetes? - -### 2.1 Ausgangslage ohne Kubernetes - -Bei klassischen Server- oder Container-Setups müssen viele Dinge manuell oder mit Skripten gelöst werden: - -- Container starten/stoppen -- Lastverteilung zwischen Instanzen -- Neustart fehlerhafter Instanzen -- Rollout neuer Versionen -- Skalierung bei Lastspitzen -- Konfigurationsmanagement und Secrets -- Self-Healing bei Fehlern - -### 2.2 Ziele von Kubernetes - -Kubernetes löst diese Probleme, indem es: - -1. **Deklaratives Modell** nutzt - - Du beschreibst den „Soll-Zustand“ (z. B. „es sollen 3 Instanzen meiner App laufen“). - - Kubernetes sorgt im Hintergrund dafür, dass dieser Zustand erreicht und gehalten wird. - -2. **Automatisierung** von: - - Rollouts und Rollbacks - - Skalierung (horizontaler Ausbau: mehr Instanzen) - - Self-Healing (Pods werden bei Absturz neu gestartet) - - Service Discovery & Load Balancing - -3. **Abstraktion von Infrastruktur**: - - Egal ob deine Knoten VMs in der Cloud oder bare-metal-Server sind – Kubernetes abstrahiert das weg. - ---- - -## 3. Hohe Ebene: Aufbau eines Kubernetes-Clusters - -Ein Kubernetes-Cluster besteht grob aus: - -- **Control Plane** (Master-Komponenten) -- **Worker Nodes** (wo die Container wirklich laufen) - -### 3.1 Control Plane (Steuerungsebene) - -Wichtige Komponenten: - -- **kube-apiserver** - - Zentraler Einstiegspunkt - - Alle Befehle von `kubectl` und anderen Komponenten laufen über die API ([[kubectl]]) - -- **etcd** - - Verteilter Key-Value-Store - - Hält den gesamten Cluster-Zustand (z. B. welche Pods, Deployments, ConfigMaps etc.) - -- **kube-scheduler** - - Entscheidet, auf welchem Node neue Pods laufen sollen - - Berücksichtigt Ressourcen (CPU, RAM), Constraints, Affinities etc. - -- **kube-controller-manager** - - Mehrere Controller (z. B. für Deployments, Nodes, Endpoints) - - Stellt sicher, dass der Ist-Zustand dem Soll-Zustand entspricht („Reconciliation Loop“) - -- **cloud-controller-manager** (optional) - - Integration mit Cloud-Anbietern (z. B. Load Balancer in AWS/Azure/GCP anlegen) - -### 3.2 Worker Nodes (Arbeitsebene) - -Auf jedem Worker laufen: - -- **kubelet** - - Agent auf dem Node - - Kommuniziert mit der API und sorgt lokal dafür, dass die Pods wie gewünscht laufen - -- **Container Runtime** - - Führt Container aus: z. B. containerd, CRI‑O, Docker (früher direkt, heute über CRI) - -- **kube-proxy** - - Implementiert das Kubernetes-Service-Netzwerk - - Kümmert sich um Weiterleitung von Netzwerkpaketen zu den richtigen Pods - ---- - -## 4. Zentrale Konzepte in Kubernetes - -### 4.1 Pod - -- Kleinste deploybare Einheit in Kubernetes -- Ein Pod enthält einen oder mehrere Container, die: - - sich IP-Adresse und Ports teilen - - sich lokale Volumes teilen -- Typischerweise: 1 Container pro Pod (plus evtl. Sidecars) - -Beispiel: Ein Pod mit einem [[NGINX]] Container: - -```yaml -apiVersion: v1 -kind: Pod -metadata: - name: my-nginx -spec: - containers: - - name: nginx - image: nginx:latest - ports: - - containerPort: 80 -``` - -### 4.2 Deployment - -- Deklarative Beschreibung von Pods + Skalierung -- Sagt z. B.: „Es sollen 3 identische Replikate dieses Pods laufen“ -- Unterstützt Rollouts und Rollbacks (z. B. `kubectl rollout undo`) - -Beispiel-Deployment: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-nginx-deployment -spec: - replicas: 3 - selector: - matchLabels: - app: my-nginx - template: - metadata: - labels: - app: my-nginx - spec: - containers: - - name: nginx - image: nginx:1.27 - ports: - - containerPort: 80 -``` - -### 4.3 Service - -- Stabile Netzwerk-Adresse (DNS-Name, virtuelle IP) für eine Gruppe von Pods -- Entkoppelt Clients von einzelnen Pod-IP-Adressen -- Typen: - - **ClusterIP**: intern im Cluster - - **NodePort**: nach außen freigegeben über einen Port jedes Nodes - - **LoadBalancer**: Integration mit einem Cloud-Load-Balancer - -Beispiel-Service: - -```yaml -apiVersion: v1 -kind: Service -metadata: - name: my-nginx-service -spec: - type: ClusterIP - selector: - app: my-nginx - ports: - - port: 80 - targetPort: 80 -``` - -### 4.4 Namespace - -- Logische Unterteilung innerhalb eines Clusters -- Dient zur: - - Trennung von Umgebungen (dev, test, prod) - - Multi-Tenancy - - Ressourcenkontrolle (ResourceQuotas, Limits) -- Standard-Namespace: `default`, daneben z. B. `kube-system`, `kube-public` - -### 4.5 ConfigMap & Secret - -- **ConfigMap**: Konfiguration (nicht sensibel), z. B. Properties, ENV-Variablen -- **Secret**: sensible Daten (Passwörter, Tokens, Zertifikate) -- Werden in Pods als ENV oder als Files (Volumes) bereitgestellt - -### 4.6 Volume & PersistentVolume - -- **Volumes**: Daten, die nicht verschwinden, wenn ein Container neu startet -- **PersistentVolume (PV)**: repräsentiert eine physische/Cloud-Speicher-Ressource -- **PersistentVolumeClaim (PVC)**: „Anforderung“ eines Pods an Speicher (Größe, Klasse) - ---- - -## 5. Wie arbeitet Kubernetes? – Das „Desired State“-Modell - -1. Du definierst Ressourcen (YAML/JSON), z. B.: - - - „Deployment mit 3 Replikas meiner App“ - - „Service, der auf Port 80 erreichbar ist“ - -2. Du übernimmt diese Definition per: - - ```bash - kubectl apply -f deployment.yaml - ``` - -3. Die API speichert diese Definition in **etcd**. - -4. Controller und Scheduler beobachten den Zustand und führen eine **Reconciliation Loop** aus: - - - Ist-Zustand (z. B. 2 Pods laufen) vs. Soll-Zustand (3 Pods sollen laufen) - - Kubernetes startet einen weiteren Pod - - Fällt ein Pod aus → wird automatisch ersetzt - ---- - -## 6. Typischer Entwickler-Workflow mit Kubernetes - -1. **Container-Image bauen** - - z. B. mit Docker oder Buildpacks - -2. **Image registrieren** - - In einer Container Registry (Docker Hub, GitHub Container Registry, ECR, GCR…) - -3. **Kubernetes-Manifest(e) schreiben** - - Deployment, Service, ConfigMap, Secret, etc. - -4. **Deployen** - - `kubectl apply -f .` - - Oder über CI/CD-Pipelines (GitLab CI, GitHub Actions, Argo CD, Jenkins etc.) - -5. **Monitoring & Logging** - - Über Tools wie Prometheus, Grafana, ELK/EFK-Stack, Loki, etc. - ---- - -## 7. Kubernetes im Ökosystem / Erweiterungen - -Kubernetes ist eine Basis, auf der vieles aufbaut: - -- **Helm** - - „Package Manager“ für Kubernetes - - Erleichtert das Installieren komplexer Anwendungen (Charts) - -- **Ingress & Ingress Controller** - - HTTP/HTTPS-Routing auf Services - - Ermöglicht virtuelle Hosts, Pfad-basiertes Routing usw. - -- **Service Mesh (z. B. Istio, Linkerd)** - - Feinere Kontrolle über Traffic, Observability, Security (mTLS) - -- **Operators** - - Kubernetes-Erweiterungen, die komplexe Anwendungen als „Custom Resources“ managen (z. B. Datenbanken) - -- **GitOps (z. B. Argo CD, Flux)** - - Zustände des Clusters werden aus Git-Repositories synchronisiert - ---- - -## 8. Vorteile von Kubernetes - -- Hohe **Portabilität** über verschiedene Infrastrukturen -- **Automatisierte Skalierung** und Self-Healing -- **Standardisierte Schnittstellen** (API-first) -- Große **Community** und reiches Ökosystem -- Gut geeignet für **Microservices** und verteile Systeme - ---- - -## 9. Herausforderungen und Komplexität - -- **Steile Lernkurve**: viele Konzepte (Pods, Deployments, Services, Ingress, RBAC, Storage…) -- **Betrieb** eines eigenen Clusters ist nicht trivial - - Viele Unternehmen nutzen Managed Services: GKE, AKS, EKS, OpenShift, Rancher etc. -- **Observability**: Logs, Metrics, Tracing sind komplexer als bei einer einzigen VM -- **Sicherheit** (RBAC, NetworkPolicies, Secrets-Management) erfordert Planung - ---- - -## 10. Wie kann man praktisch starten? - -Konkrete Schritte, um anzufangen: - -1. **Lokale Umgebung**: - - `kind` (Kubernetes in Docker) - - `minikube` oder `k3d` - -2. **Übungen**: - - Einen einfachen Webserver als Deployment & Service bereitstellen - - Mit `kubectl get`, `describe`, `logs` spielen - - Scaling testen: `kubectl scale deployment my-deployment --replicas=5` - - Eine neue Version deployen und Rollback ausprobieren - -3. **Doku & Lernressourcen**: - - Offizielle Doku: https://kubernetes.io/docs/ - - Interaktive Tutorials: Katacoda-ähnliche Labs, Kubernetes Bootcamps, z. B. von Cloud-Anbietern diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Layer 7 Logik.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Layer 7 Logik.md deleted file mode 100755 index 82a0ee3..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Layer 7 Logik.md +++ /dev/null @@ -1,25 +0,0 @@ -„Layer‑7‑Logik“ bezieht sich auf Entscheidungen/Routing auf Basis der **Anwendungsebene** (Layer 7 im OSI-Modell). - -Kurz zum Kontext: - -- **Layer 3 (Netzwerk)**: IP-Adressen -- **Layer 4 (Transport)**: TCP/UDP, Ports (z. B. Port 80, 443) -- **Layer 7 (Anwendung)**: HTTP, HTTPS, gRPC, SMTP etc. – also „Inhalt“ der Anfrage - -**Layer‑4‑Routing**: -Es wird nur nach IP + Port entschieden. Beispiel: -- Alles an `10.0.0.5:80` geht zu Service A. -Der Proxy/Loadbalancer „sieht“ nicht, ob das `/api` oder `/shop` ist. - -**Layer‑7‑Logik**: -Der Proxy versteht das Protokoll (z. B. HTTP) und kann anhand von Anwendungsdaten entscheiden: - -- Hostname: `api.example.com` → Service A, `shop.example.com` → Service B -- Pfad: `/api` → Backend 1, `/app` → Backend 2 -- HTTP-Header: bestimmter `User-Agent`, `X-Feature-Flag` → anderes Routing -- Cookies / Session: z. B. „User ist in A/B-Test-Gruppe → anderes Backend“ -- Authentifizierung / [[JWT token]] prüfen, bevor weitergeleitet wird -- TLS-Termination: HTTPS entschlüsseln, weiter innen nur HTTP sprechen - -Im Kubernetes-Ingress-Kontext heißt „Layer‑7‑Logik“ also: -Der Ingress-Controller trifft Routing- und Sicherheitsentscheidungen **auf Basis von HTTP(S)-Details** (Host, Pfad, Header, Cookies usw.), nicht nur auf Basis von IPs und Ports. \ No newline at end of file diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Nginx.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Nginx.md deleted file mode 100755 index 212c0d1..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Nginx.md +++ /dev/null @@ -1,430 +0,0 @@ -## 1. Grundidee: Was ist nginx – gerade im Python‑Kontext? - -nginx („engine x“) ist ein **Webserver** und **Reverse Proxy**, der: - -- HTTP(S)-Anfragen aus dem Internet annimmt -- statische Dateien (HTML, CSS, Bilder, JS) direkt ausliefert -- Anfragen für **dynamische Inhalte** (z.B. deine Python‑App) an einen **Anwendungsserver** weiterleitet (z.B. Gunicorn, uWSGI, [[uvicorn]]) - -Im Python‑Kontext wird nginx fast immer als **„Vorderseite“ (Frontend) vor einer Python‑Webanwendung** eingesetzt. - -Typischer Aufbau: - -```text -Browser des Nutzers -> nginx -> Gunicorn/uWSGI/uvicorn -> Python-App (Django, Flask, FastAPI, ...) - (Port 80/443) (lokaler Port, z.B. 8000) -``` - - ---- - -## 2. Einordnung in die Webwelt: Grundbegriffe - -Damit nginx einzuordnen ist, müssen ein paar Begriffe klar sein: - -### 2.1 Webserver - -Ein **Webserver**: - -- läuft auf einem Rechner (Server) -- „lauscht“ auf Port 80 (HTTP) oder 443 (HTTPS) -- nimmt HTTP-Anfragen entgegen und schickt HTTP-Antworten zurück - -Beispiele: -- **nginx** -- **Apache HTTP Server** -- (leichtere) Caddy, lighttpd - -### 2.2 Application Server / WSGI-/ASGI-Server - -Ein **Application Server** führt deine **Web-App** aus, also deinen Python-Code. - -- Für klassische synchronen Python-Webapps: **WSGI**-Server - - z.B. **[[Gunicorn]]**, **uWSGI**, mod_wsgi -- Für moderne, asynchrone Apps: **ASGI**-Server - - z.B. **[[uvicorn]]**, **[[hypercorn]]**, Daphne - -Der App-Server: - -- spricht mit deinem Python-Framework (Django, Flask, [[FastAPI]], etc.) -- versteht Anfragen im WSGI/ASGI-Format -- erzeugt dynamische Antworten (z.B. JSON, HTML aus Templates) - -### 2.3 Python-Webframework (Django, Flask, [[FastAPI]] …) - -Ein **Framework** ist deine Programmierbibliothek, mit der du die Web-App baust: - -- Django: „Vollausstattung“ (ORM, Admin, Auth, etc.) -- Flask: minimaler Kern, viel per Erweiterung -- [[FastAPI]]: modern, async, stark bei APIs - -**Das Framework ist nicht der Webserver.** -Es definiert nur, wie dein Code auf Anfragen reagieren soll. - ---- - -## 3. Abgrenzung: nginx vs. verwandte Begriffe - -### 3.1 nginx vs. Apache - -Beide sind **Webserver**, Unterschiede grob: - -- **nginx** - - schnell bei vielen gleichzeitigen Verbindungen - - ressourcensparend (eventbasiert) - - sehr gut als Reverse Proxy, Load Balancer, TLS-Terminator -- **Apache** - - älter, sehr weit verbreitet - - modulbasiert, viel Legacy - - kann PHP & Co. direkt ausführen (mod_php, etc.) - -Im modernen Python-Umfeld: **nginx + Gunicorn/[[uvicorn]]** ist ein sehr gängiges Setup. - -### 3.2 nginx vs. [[Gunicorn]]/uWSGI/[[uvicorn]] - -- **nginx**: Webserver / Reverse Proxy -- **[[Gunicorn]]/uWSGI/[[uvicorn]]**: Python-Application-Server - -nginx: - -- kümmert sich um HTTP(S), TLS, statische Dateien, Load Balancing -- schickt Anfragen an den Application-Server weiter - -[[Gunicorn]]/[[uvicorn]]: - -- startet deine Python-App -- verarbeitet die Anfragen logikseitig -- rendert Templates, greift auf Datenbanken zu, etc. - -### 3.3 nginx vs. Django/Flask/[[FastAPI]] - -- nginx: Infrastruktur-Komponente (Server) -- Django/Flask/[[FastAPI]]: Bibliotheken, mit denen du deinen Code schreibst - -Eine typische Kombi: - -```text -nginx + Gunicorn + Django -nginx + uvicorn + FastAPI -nginx + uWSGI + Flask -``` - -Jede Komponente hat ihren klaren Aufgabenbereich. - - ---- - -## 4. Welche Probleme löst nginx im Python-Setup? - -### 4.1 Sichere und stabile Auslieferung deiner Python-Webapp - -Problem: -Die eingebaute Entwicklungsserver in Django/Flask sind nur für **lokale Entwicklung** gedacht: - -- wenig performant -- kaum Sicherheitsfeatures -- nicht für viele gleichzeitige Nutzer ausgelegt - -Lösung: -nginx + App-Server (Gunicorn/uvicorn): - -- nginx nimmt alle Anfragen entgegen -- Python-App läuft hinter nginx auf einem internen Port -- Anwendung ist stabiler, sicherer, skalierbarer - -### 4.2 Auslieferung statischer Dateien - -Problem: -Python-Frameworks sind nicht optimiert, um Millionen von statischen Dateien schnell auszuliefern. - -Lösung: - -- nginx kann statische Dateien extrem effizient ausliefern -- Python-App wird entlastet - -**Beispiel:** Django-Setup: - -```text -Browser -> nginx -> (statische Dateien: /static/) direkt von nginx - -> (dynamische URLs: /api/, /admin/) an Gunicorn/Django -``` - -### 4.3 HTTPS / TLS-Termination - -Problem: -Du willst deine Seite unter **https://deinedomain.de** mit Zertifikat ausliefern. -Python-Application-Server kümmern sich eher nicht um Zertifikate. - -Lösung: - -- nginx bietet HTTPS/TLS an (inkl. Let’s Encrypt-Integration) -- entschlüsselt eingehende Anfragen -- leitet sie dann intern als HTTP an Gunicorn/uvicorn weiter - -So muss sich deine Python-App nicht um Zertifikate kümmern. - -### 4.4 Load Balancing (Lastverteilung) - -Problem: -Eine einzelne Instanz deiner Python-App reicht nicht bei hoher Last. - -Lösung: - -- nginx kann Anfragen auf mehrere App-Server verteilen: - -```text -Browser -> nginx -> Gunicorn Instanz 1 - -> Gunicorn Instanz 2 - -> Gunicorn Instanz 3 -``` - -Damit erreichst du höhere Verfügbarkeit und Kapazität. - -### 4.5 Reverse Proxy / Schutz der App - -Problem: -Du willst deine App nicht direkt ins Internet hängen (Sicherheitsrisiko, private Ports). - -Lösung: - -- nginx steht „davor“ und leitet nur definierte Anfragen durch -- interne Ports (z.B. 8000) sind von außen nicht erreichbar -- nginx kann zusätzliche Security-Header, Ratelimits, IP-Filter etc. einsetzen - - ---- - -## 5. Herausforderungen bei der Arbeit mit nginx - -### 5.1 Konfigurationssyntax & -struktur - -- nginx wird über Textdateien konfiguriert (z.B. `/etc/nginx/nginx.conf` und `sites-available/...`) -- die Syntax ist am Anfang ungewohnt: - - `http`, `server`, `location`, `upstream`, `listen`, etc. -- Fehler in der Konfiguration führen dazu, dass nginx nicht startet oder falsch weiterleitet - -Praxis-Hinweise: - -```bash -sudo nginx -t # Konfiguration testen -sudo systemctl reload nginx # Konfiguration neu laden -``` - -### 5.2 Rechte & Pfade (Linux-Dateisystem) - -Probleme: - -- nginx läuft als bestimmter Benutzer (z.B. `www-data`) -- Python-App läuft evtl. als anderer Benutzer (z.B. `myapp`) -- Zugriffsrechte auf Dateien/Ordner (Logs, statische Dateien) müssen passen - -Beispiel: -Wenn statische Dateien unter `/var/www/myapp/static/` liegen, muss der nginx-User Lesezugriff haben. - -### 5.3 Fehlersuche (Debugging) - -- Fehler können an mehreren Stellen auftreten: - - DNS / Domain falsch - - nginx-Konfiguration fehlerhaft - - App-Server (Gunicorn/uvicorn) läuft nicht - - Python-App wirft Fehler - -Praxis-Tipp: - -- in nginx-Logs schauen: - - `/var/log/nginx/access.log` - - `/var/log/nginx/error.log` -- in App-Logs schauen -- Ports prüfen: `ss -tulpen` oder `netstat -tulpen` - -### 5.4 Performance-Tuning - -Fortgeschrittener, aber wichtig bei größerem Traffic: - -- wie viele Worker-Prozesse nginx nutzt -- Keep-Alive-Einstellungen -- Buffergrößen -- Cache-Konfiguration für statische Inhalte - -Am Anfang reicht meist die Standardkonfiguration, später optimiert man bei Bedarf. - - ---- - -## 6. Praxisnahe Beispiele - -### 6.1 Minimalbeispiel: Flask-App hinter nginx + Gunicorn - -**1. Eine einfache Flask-App (Python)** - -Datei: `app.py` - -```python -from flask import Flask - -app = Flask(__name__) - -@app.route("/") -def index(): - return "Hallo, Welt! Das kommt aus Flask hinter nginx." -``` - -Start des App-Servers (Gunicorn): - -```bash -pip install flask gunicorn -gunicorn -w 3 -b 127.0.0.1:8000 app:app -# -w 3: 3 Worker -# -b 127.0.0.1:8000: lauscht auf Port 8000 nur lokal -``` - -**2. nginx-Konfiguration (vereinfacht)** - -Datei: z.B. `/etc/nginx/sites-available/myflaskapp`: - -```nginx -server { - listen 80; - server_name example.com; # deine Domain oder IP - - # Statische Dateien (wenn du welche hast) - # root /var/www/myflaskapp; # falls du HTML/Assets direkt ablegen willst - - location / { - proxy_pass http://127.0.0.1:8000; # Gunicorn - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Dann: - -```bash -sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl reload nginx -``` - -Ablauf: - -1. Browser ruft `http://example.com/` auf -2. nginx nimmt Anfrage auf Port 80 entgegen -3. nginx leitet an `http://127.0.0.1:8000` weiter (Gunicorn/Flask) -4. Flask antwortet mit „Hallo, Welt“ -5. nginx sendet diese Antwort an den Browser zurück - - -### 6.2 Beispiel: Django mit statischen Dateien - -**1. Django-Setup (stark vereinfacht)** - -In `settings.py`: - -```python -STATIC_URL = "/static/" -STATIC_ROOT = "/var/www/mydjangoproject/static/" -``` - -Danach statische Dateien sammeln: - -```bash -python manage.py collectstatic -``` - -**2. nginx-Konfiguration (Ausschnitt)** - -```nginx -server { - listen 80; - server_name example.com; - - # Statische Dateien direkt von nginx - location /static/ { - alias /var/www/mydjangoproject/static/; - access_log off; - expires 30d; - } - - # Alles andere an Django via Gunicorn - location / { - proxy_pass http://127.0.0.1:8001; # hier läuft Gunicorn - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Vorteil: - -- statische Inhalte kommen extrem schnell von nginx -- Django/Gunicorn verarbeitet nur „echte“ App-Logik - - -### 6.3 Beispiel: HTTPS mit Let’s Encrypt (Konzept) - -Mit Tools wie **certbot** kannst du für nginx kostenlos TLS-Zertifikate holen. - -Grobe Schritte: - -```bash -sudo apt install certbot python3-certbot-nginx -sudo certbot --nginx -d example.com -``` - -certbot: - -- holt Zertifikat -- passt nginx-Konfiguration an (Port 443, SSL-Parameter) -- richtet automatische Erneuerung ein - -nginx-Konfiguration enthält danach z.B.: - -```nginx -server { - listen 443 ssl; - server_name example.com; - - ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; - - location / { - proxy_pass http://127.0.0.1:8000; - ... - } -} -``` - -Damit ist deine Python-App per HTTPS erreichbar. - ---- - -## 7. Zusammenfassung - -- **nginx** ist ein leistungsfähiger Webserver und Reverse Proxy, der im Python-Kontext typischerweise: - - vor deiner Python-Webanwendung sitzt - - HTTP(S) abwickelt - - statische Dateien effizient ausliefert - - Anfragen an einen Python-Application-Server (Gunicorn, uWSGI, uvicorn) weiterleitet - - TLS/HTTPS, Load Balancing, Security-Features bereitstellt - -- **Abgrenzung**: - - nginx ≠ Python-Framework (Django/Flask/[[FastAPI]]) - - nginx ≠ Application-Server (Gunicorn/uvicorn) - - nginx = Webserver/Reverse Proxy - -- **Gelöste Probleme**: - - sichere Produktion anstelle von Entwicklungsservern - - Performance für statische Inhalte - - TLS/HTTPS, Lastverteilung, Schutz der App - -- **Herausforderungen**: - - Konfigurationsdateien verstehen - - Rechte & Pfade korrekt setzen - - Fehler systematisch debuggen - - bei hoher Last Performance tunen - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/OAuth2.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/OAuth2.md deleted file mode 100755 index eaa1b61..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/OAuth2.md +++ /dev/null @@ -1,360 +0,0 @@ -Im Folgenden bekommst du eine Einführung in OAuth 2, so dass du danach: - -- weißt, was OAuth 2 grundsätzlich ist, -- es von verwandten Konzepten wie „Login mit Google“, OpenID Connect oder reiner Authentifizierung unterscheiden kannst, -- verstehst, welche Probleme OAuth 2 löst – und welche neuen Herausforderungen es mit sich bringt, -- und ein paar praxisnahe Szenarien im Kopf hast. - ---- - -## 1. Grundsätzliche Definition: Was ist OAuth 2? - -**Kurze Definition**: -OAuth 2 ist ein *Autorisierungs*-Framework. Es regelt, wie eine Anwendung (Client) **im Namen eines Benutzers** sicher auf Ressourcen (z. B. APIs, Daten) zugreifen kann, **ohne dass die Anwendung das Passwort des Benutzers kennen muss**. - -Es geht also primär um: - -- **Zugriffsrechte (Authorization)** -- **Delegation**: „Ich (Nutzer) erlaube App X, auf meine Daten bei Dienst Y zuzugreifen.“ - -### Zentrales Prinzip - -Statt: -- Benutzer gibt sein Passwort direkt bei jeder Drittanbieter-App ein - -nutzt man: -- Benutzer loggt sich beim **vertrauenswürdigen Dienst** ein (z. B. Google, GitHub, dein Unternehmens-Identity-Server) -- Dieser Dienst stellt ein **Token** aus (eine Art Eintrittskarte) -- Drittanbieter-App nutzt dieses Token, um beim API-Server Zugriff zu bekommen - -Dadurch muss die Drittanbieter-App nie das Passwort sehen – sie bekommt **nur einen begrenzten, kontrollierten Zugriff**. - ---- - -## 2. Die wichtigsten Rollen in OAuth 2 - -Um Beispiele zu verstehen, braucht man die Standardrollen: - -1. **Resource Owner** - Der Besitzer der Daten/Ressourcen – meist der **Benutzer**. - -2. **Client** - Die Anwendung, die im Namen des Benutzers zugreifen möchte - (z. B. eine mobile App, eine Web-App, ein Backend-Service). - -3. **Resource Server** - Die API/das System, das die geschützten Ressourcen bereitstellt - (z. B. Google Calendar API, GitHub API, Unternehmens-REST-API). - -4. **Authorization Server** - Der Server, der: - - den Benutzer authentifiziert (z. B. Login-Formular) und - - **Tokens** ausstellt (Access Tokens, optional Refresh Tokens). - -Oft sind Authorization Server und Resource Server derselbe technische Dienst, aber **konzeptionell** werden sie getrennt betrachtet. - ---- - -## 3. Grund-Mechanik: Wie läuft ein OAuth-2-Flow ab? - -Stark vereinfacht am typischen Web-Szenario: - -1. Benutzer nutzt eine Drittanbieter-App (Client) und klickt z. B. auf: - „Verbinde dich mit meinem Google-Kalender“. - -2. Client leitet den Browser zum **Authorization Server** (z. B. accounts.google.com) weiter: - - mit Angaben wie: „Diese App möchte Zugriff auf Kalenderdaten“. - -3. Benutzer loggt sich **beim Authorization Server** ein (falls nicht schon eingeloggt). - -4. Benutzer bekommt eine Seite angezeigt: - „Die Anwendung X möchte auf Ihre Kalenderdaten zugreifen. Erlauben? (Ja/Nein)“ - -5. Wenn Benutzer zustimmt: - - Authorization Server schickt den Benutzer zurück zur App - - Die App bekommt einen **Authorization Code** (bei einem gängigen Flow). - -6. Die App tauscht diesen Code im Hintergrund beim Authorization Server gegen ein **Access Token** (und evtl. ein **Refresh Token**) ein. - -7. Die App nutzt das Access Token, um beim **Resource Server** (z. B. Google Calendar API) Daten abzufragen. - -Der Benutzer muss sein **Passwort nur beim Authorization Server** eingeben – nicht bei der Drittanbieter-App. - ---- - -## 4. Abgrenzung zu ähnlichen / verwandten Begriffen - -### 4.1 OAuth 2 (Authorization) vs. Authentifizierung (Login) - -**Authentifizierung**: „Wer bist du?“ -**Autorisierung**: „Worauf darfst du zugreifen?“ - -- OAuth 2 löst formal **Autorisierung** (Delegation von Zugriffsrechten). -- Viele Systeme nutzen OAuth 2 aber **im Zusammenhang mit Login**, was verwirrend sein kann. - -Ein Login mit Google, bei dem eine Anwendung am Ende weiß „dieser User ist Max Mustermann“, basiert meist auf **OpenID Connect**, das **auf OAuth 2 aufsetzt**. - -### 4.2 OAuth 2 vs. OpenID Connect (OIDC) - -- **OAuth 2**: Spezifiziert, wie Clients **Access Tokens** bekommen, um auf APIs zuzugreifen. -- **OpenID Connect**: Erweiterung auf Basis von OAuth 2, um **Identität** zu transportieren. - -OIDC liefert zusätzlich: -- ein **ID Token** (meist ein [[JWT token]]), das Informationen wie: - - User-ID - - E-Mail - - Name - enthält. - -Praxis-Beispiele: - -- „Mit Google anmelden“ (Login) → **OpenID Connect** -- „Google Kalender-API nutzen“ → **OAuth 2** (Autorisierung für API-Zugriff) - -### 4.3 OAuth 2 vs. API-Key - -**API-Key**: -- Meist ein statischer Schlüssel, der im Client eingebaut ist. -- Unterscheidet nicht zwischen verschiedenen Benutzern. -- Hat wenig feingranulare Rechte (oft: „alles oder nichts“). -- Sicherheitsrisiko bei Leaks (z. B. im Frontend-Code). - -**OAuth 2**: -- Nutzerbezogen (Access Token repräsentiert den Benutzer/das Delegationsrecht). -- Token haben meist begrenzte Lebensdauer. -- Zugriffsrechte (Scopes) sind feiner steuerbar. - -### 4.4 OAuth 2 vs. SAML - -- **SAML**: Älterer Standard, XML-basiert, häufig in Unternehmensumgebungen für SSO verwendet. -- **OAuth 2 / OIDC**: JSON/HTTP-basiert, leichtergewichtig, stark im Web- und Mobile-Bereich verbreitet. - -Grober Merksatz: -- SAML: klassische Unternehmen, Single Sign-On im Browser, ältere Infrastruktur. -- OAuth 2 + OIDC: moderne Web-/Mobile-Apps, REST-APIs, Microservices. - ---- - -## 5. Welche Probleme löst OAuth 2? - -### 5.1 Vermeidung von Passwortweitergabe an Drittanbieter - -Früher: -Eine App wollte auf Gmail zugreifen => Benutzer musste **sein Gmail-Passwort** in dieser App eingeben. -Risiken: -- App könnte Passwort speichern oder missbrauchen. -- Bei Passwortänderung müsste der Benutzer alles neu einrichten. -- Anbieter (Google) hat keine Kontrolle, welche App das tut. - -Mit OAuth 2: -- Benutzer loggt sich nur bei Google ein. -- Drittanbieter-App erhält nur ein **Token mit begrenzten Rechten**, kein Passwort. - -**Praxisbeispiel**: -Eine To-Do-App möchte deine Google Kalender-Einträge anzeigen: -Du klickst „Mit Google verbinden“, wirst zu Google geleitet, bestätigst die Berechtigungen und die App sieht danach deine Kalender – ohne dein Passwort zu kennen. - -### 5.2 Feingranulare Zugriffsrechte (Scopes) - -Mit **Scopes** kann eingeschränkt werden, worauf eine App zugreifen darf. - -Beispiele: -- `read_calendar`: nur Leserechte auf Kalender -- `write_calendar`: Schreibrechte auf Kalender -- `email`: Zugriff auf deine E-Mail-Adresse -- `profile`: Zugriff auf deine Basisprofildaten - -So kann der Benutzer (und auch der Resource Server) klar kontrollieren: -- Eine App darf z. B. **nur lesen**, nicht schreiben. -- Eine App darf **nur auf E-Mail und Profil**, nicht auf Kontakte oder Drive zugreifen. - -### 5.3 Zeitlich begrenzte Tokens - -**Access Tokens** sind typischerweise kurzlebig (z. B. 5–60 Minuten). - -- Wenn ein Token geleakt wird, ist der Schaden zeitlich begrenzt. -- Über Revocation-Mechanismen kann der Zugriff vorzeitig beendet werden. - -Optional gibt es **Refresh Tokens**: -- Länger gültig -- Dienen dazu, neue Access Tokens zu holen -- Können vom Authorization Server gezielt deaktiviert werden (z. B. bei Kompromittierung). - -### 5.4 Single Sign-On / bessere User Experience (in Kombination mit OIDC) - -In Kombination mit OIDC und Single-Sign-On-Konzepten: -- Benutzer loggt sich einmal beim zentralen Identity Provider ein. -- Mehrere Anwendungen können dann in einer Session auf Tokens zugreifen, ohne den Benutzer jedes Mal neu einzuloggen. - ---- - -## 6. Typische OAuth-2-Flows (vereinfacht) - -Es gibt verschiedene **„Flows“**, je nach Art des Clients und Nutzungsszenario. Die wichtigsten: - -### 6.1 Authorization Code Flow (heute mit PKCE) - -Typisch für: -- Web-Anwendungen mit Backend -- Single Page Applications (mit PKCE) -- Mobile Apps - -Ablauf (vereinfacht, ohne technische Details): - -1. Anwendung leitet Benutzer zum Authorization Server. -2. Benutzer loggt sich ein und stimmt zu. -3. Authorization Server schickt **Authorization Code** an die Anwendung (über Redirect). -4. Anwendung tauscht Code gegen ein **Access Token** (und optional Refresh Token). - -**PKCE** (Proof Key for Code Exchange) ist eine Sicherheits-Erweiterung, um Code-Diebstahl zu verhindern, insbesondere bei mobilen/Browser-basierten Apps. - -### 6.2 Client Credentials Flow - -Für: -- Machine-to-Machine-Kommunikation (kein Benutzer) -- Hintergrundprozesse, Microservices, Server-zu-Server - -Hier gibt es keinen Benutzer. -Der Client identifiziert sich selbst (z. B. mit Client ID + Client Secret) beim Authorization Server und bekommt ein Access Token, um auf eine API zuzugreifen. - -**Beispiel**: -Ein Backend-Service ruft periodisch eine interne Unternehmens-API auf, um Daten zu synchronisieren. - -### 6.3 Device Code Flow - -Für Geräte ohne vollwertigen Browser oder ohne komfortable Eingabemöglichkeit, wie: -- Smart-TVs -- Konsolen -- IoT-Geräte - -Ablauf: -- Gerät zeigt einen Code und eine URL an (z. B. „Bitte gehen Sie auf https://example.com/device und geben Sie Code ABCD ein“). -- Benutzer geht an seinem Smartphone/PC auf diese URL, loggt sich dort ein und bestätigt. -- Gerät bekommt anschließend ein Token und kann auf die Ressource zugreifen. - ---- - -## 7. Herausforderungen und typische Stolperfallen - -### 7.1 OAuth 2 ist komplex und flexibel - -OAuth 2 ist sehr flexibel – das ist Stärke und Schwäche zugleich: - -- Viele Optionen & Erweiterungen → schnell unübersichtlich. -- Viele Entscheidungen zu treffen: Flows, Token-Formate, Scopes, Lifetimes, PKCE, etc. - -Für Einsteiger wirkt es oft „überdimensioniert“, vor allem wenn man nur „einen einfachen Login“ braucht (wo eigentlich OIDC gefragt ist). - -### 7.2 Verwechslung von OAuth 2 und Login - -Weil viele große Anbieter beides kombiniert haben („Login mit Google“), entsteht oft: - -- „OAuth 2 ist ein Login-Protokoll.“ → **Formal falsch**, es ist ein Autorisierungsframework. -- Für „Login“ verwendet man **OpenID Connect**, auch wenn technisch OAuth-2-Mechanismen verwendet werden. - -Konsequenz: -Fehlkonzeptionen in Projekten, z. B. versucht man reine OAuth-2-Tokens zu nutzen, um Identität abzuleiten, statt OIDC zu verwenden. - -### 7.3 Sicherheitsrisiken bei falscher Implementierung - -Einige typische Fehler: - -- **Tokens in URLs weitergeben** → landen in Logs, Browser-History, Referrer-Headern. -- **Implicit Flow** für Single-Page-Apps ohne ausreichende Sicherheitsmaßnahmen - → gilt inzwischen als überholt; empfohlen wird Authorization Code Flow mit PKCE. -- **Keine Einschränkung der Redirect-URIs** → Angreifer können Redirect-URIs manipulieren und Tokens abfangen. -- **Zu lange gültige Tokens** → höheres Risiko bei Leaks. -- **Speichern von Tokens im unsicheren Speicher** (z. B. localStorage in Browsern) → anfällig für XSS. - -### 7.4 Komplexität für Endnutzer - -Auch für Endnutzer ist die Zustimmung manchmal nicht klar: - -- „Diese App möchte auf Ihr Profil, Ihre Kontakte, Ihre Dateien, Ihre E-Mails zugreifen.“ - → Viele klicken aus Gewohnheit auf „Zulassen“, ohne zu verstehen, was das bedeutet. - -Das ist eher ein UX-/Privacy-Thema, aber eng mit OAuth 2 verbunden, da die Darstellung der **Scopes** oft technisch und verwirrend ist. - ---- - -## 8. Praxisnahe Beispiele - -### Beispiel 1: „Mit Google anmelden“ in einer Web-App - -**Situation**: -Du entwickelst eine Web-App, in der sich Nutzer anmelden können. Statt ein eigenes Benutzerkontensystem aufzubauen, willst du „Login mit Google“ anbieten. - -**In Wahrheit passiert (typisch)**: -- Die App nutzt **OpenID Connect**: - - OAuth 2 regelt den Token-Flow. - - OIDC liefert ein ID Token mit Infos über den Benutzer. -- Nutzer sieht: Google-Login, eine Consent-Seite, und wird danach in deine App zurückgeleitet. -- Deine App bekommt: - - ein **ID Token** (zur Identifikation des Nutzers) - - ein **Access Token** (um z. B. zusätzlich auf Google APIs zuzugreifen, falls gewünscht). - -**OAuth-2-Aspekt**: -Der eigentliche Mechanismus, wie Access Token ausgestellt werden, ist OAuth 2. - -### Beispiel 2: Drittanbieter-Tool liest GitHub-Repositories - -**Situation**: -Ein Build-Tool oder eine CI/CD-Plattform möchte deine privaten GitHub-Repositories lesen, um Builds auszulösen. - -Ablauf: -1. In der CI/CD-Plattform klickst du „Mit GitHub verbinden“. -2. Du wirst zu GitHub weitergeleitet, loggst dich ein. -3. GitHub zeigt dir eine Seite: - „Tool X möchte Zugriff auf: - - Ihre öffentlichen und privaten Repositories - - Ihre Webhooks - …“ -4. Du erlaubst. -5. Die CI/CD-Plattform erhält ein Access Token mit bestimmten **Scopes** (z. B. `repo`). - -**OAuth-2-Aspekt**: -- Das Access Token erlaubt der CI/CD-Plattform, im Namen deines GitHub-Accounts zu agieren, innerhalb des erlaubten Scopes. - -### Beispiel 3: Unternehmensinternes Microservice-System - -**Situation**: -In einer Microservice-Architektur gibt es: -- einen zentralen Authorization Server (z. B. Keycloak, Auth0, Azure AD), -- viele Services, die interne APIs bereitstellen. - -Szenario: -- Ein Frontend bekommt vom Authorization Server (über einen Login) ein Access Token. -- Dieses Access Token wird vom Frontend bei Aufrufen an das API-Gateway oder direkt an Microservices mitgeschickt. -- Microservices verifizieren das Token und entscheiden anhand von Scopes/Rollen, ob der Zugriff erlaubt ist. - -**OAuth-2-Aspekt**: -- Zugriffskontrolle wird zentralisiert. -- Jeder Service muss nicht mehr selbst Benutzer verwalten, sondern nur Access Tokens prüfen. - ---- - -## 9. Zusammenfassung in Stichpunkten - -- **OAuth 2** ist ein **Autorisierungs-Framework** (nicht direkt ein Login-Protokoll). -- Es ermöglicht, dass **Drittanbieter-Apps im Namen eines Nutzers** auf Ressourcen zugreifen können, ohne das Passwort des Nutzers zu kennen. -- Wichtige Rollen: **Resource Owner**, **Client**, **Resource Server**, **Authorization Server**. -- Es arbeitet mit **Tokens** (Access Tokens, optional Refresh Tokens) und **Scopes** (feingranulare Rechte). -- Verwandte Standards: - - **OpenID Connect**: für Authentifizierung/Identität (baut auf OAuth 2 auf). - - **SAML**: alternative, ältere Lösung v. a. für SSO in Unternehmen. -- Es löst Probleme wie: - - Passwortweitergabe an Drittanbieter - - Fehlende Feingranularität von Zugriffsrechten - - Mangelnde Kontrolle über Zugriffsdelegation -- Herausforderungen: - - Komplexität und viele Optionen - - Verwechslungsgefahr mit Authentifizierung - - Sicherheitsrisiken bei falscher Implementierung - - UX/Verständlichkeit von Zustimmungsdialogen -- Praxis: - - „Mit Google/GitHub/Facebook anmelden“ - - Drittanbieter-Apps, die auf APIs zugreifen - - Microservice-Systeme mit zentralem Identity & Access Management - ---- - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Terraform.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Terraform.md deleted file mode 100755 index ac0664e..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/Terraform.md +++ /dev/null @@ -1,530 +0,0 @@ -#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. - ---- - diff --git a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/kubectl.md b/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/kubectl.md deleted file mode 100755 index 86b4997..0000000 --- a/notes/nextcloud/Notes/IT-Know-How/web und cloud gedöns/kubectl.md +++ /dev/null @@ -1,358 +0,0 @@ -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 [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. \ No newline at end of file diff --git a/sync_notes.sh b/sync_notes.sh deleted file mode 100755 index 3ae3055..0000000 --- a/sync_notes.sh +++ /dev/null @@ -1 +0,0 @@ -rsync -aP /c/Users/d92448/OneDrive\ -\ d-fine\ GmbH/Notizbücher/mathias_dfine_vault/mathias_dfine_vault/Know-How/ ~/dotfiles/notes/nextcloud/Notes/IT-Know-How/