update
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
# 🧩 Was ist Ansible?
|
||||
|
||||
**Ansible** ist ein Open-Source-Tool zur **Automatisierung von IT-Aufgaben**. Es wird vor allem genutzt für:
|
||||
|
||||
* **Konfigurationsmanagement** (Server einrichten)
|
||||
* **Deployment** (Software ausrollen)
|
||||
* **Orchestrierung** (komplexe Abläufe steuern)
|
||||
|
||||
👉 Einfach gesagt:
|
||||
Mit Ansible kannst du **wiederholbare Aufgaben automatisieren**, die sonst manuell auf Servern ausgeführt werden müssten.
|
||||
|
||||
💡 Besonderheit:
|
||||
Ansible ist **agentenlos** – es muss nichts auf den Zielsystemen installiert werden (nur z. B. SSH-Zugang bei Linux).
|
||||
|
||||
---
|
||||
|
||||
# 🧠 Grundprinzip (einfach erklärt)
|
||||
|
||||
Ansible arbeitet mit sogenannten **Playbooks** (YAML-Dateien), in denen du beschreibst:
|
||||
|
||||
> „Was soll passieren?“ – nicht „Wie genau Schritt für Schritt?“
|
||||
|
||||
Beispiel (vereinfacht):
|
||||
|
||||
```yaml
|
||||
- hosts: webserver
|
||||
tasks:
|
||||
- name: Installiere Nginx
|
||||
apt:
|
||||
name: nginx
|
||||
state: present
|
||||
```
|
||||
|
||||
👉 Bedeutung:
|
||||
|
||||
* Ziel: Servergruppe „webserver“
|
||||
* Aufgabe: Installiere Nginx, falls noch nicht vorhanden
|
||||
|
||||
➡️ Wichtig: **idempotent**
|
||||
→ Der gleiche Befehl kann beliebig oft laufen, ohne Schaden anzurichten.
|
||||
|
||||
---
|
||||
|
||||
# 🔍 Abgrenzung zu ähnlichen Tools
|
||||
|
||||
Ansible gehört zur Kategorie „Infrastructure as Code“. Hier ein Vergleich:
|
||||
|
||||
### 🆚 Puppet
|
||||
|
||||
* arbeitet mit Agenten auf Zielsystemen
|
||||
* eigene DSL (Programmiersprache)
|
||||
* komplexer Einstieg
|
||||
|
||||
👉 Ansible:
|
||||
|
||||
* kein Agent nötig
|
||||
* nutzt YAML (einfacher lesbar)
|
||||
|
||||
---
|
||||
|
||||
### 🆚 Chef
|
||||
|
||||
* basiert stark auf Ruby
|
||||
* eher „programmatisch“
|
||||
|
||||
👉 Ansible:
|
||||
|
||||
* deklarativ („Zielzustand beschreiben“)
|
||||
|
||||
---
|
||||
|
||||
### 🆚 Terraform
|
||||
|
||||
* erstellt Infrastruktur (Cloud, Netzwerke)
|
||||
* Fokus: „Was existiert?“
|
||||
|
||||
👉 Ansible:
|
||||
|
||||
* konfiguriert Systeme („Was läuft darauf?“)
|
||||
|
||||
💡 Typische Kombination:
|
||||
|
||||
* Terraform erstellt Server
|
||||
* Ansible konfiguriert sie
|
||||
|
||||
---
|
||||
|
||||
### 🆚 Docker
|
||||
|
||||
* isoliert Anwendungen in Containern
|
||||
|
||||
👉 Ansible:
|
||||
|
||||
* kann Docker automatisieren (z. B. Container starten)
|
||||
|
||||
---
|
||||
|
||||
# 🧩 Welche Probleme löst Ansible?
|
||||
|
||||
## 1. ❌ „Works on my machine“-Problem
|
||||
|
||||
Unterschiedliche Umgebungen führen zu Bugs.
|
||||
|
||||
👉 Lösung:
|
||||
|
||||
* identische Konfiguration überall
|
||||
|
||||
---
|
||||
|
||||
## 2. ❌ Manuelle Server-Konfiguration
|
||||
|
||||
Admins klicken sich durch Systeme → fehleranfällig
|
||||
|
||||
👉 Lösung:
|
||||
|
||||
* alles als Code definieren
|
||||
|
||||
---
|
||||
|
||||
## 3. ❌ Deployment-Chaos
|
||||
|
||||
Unklare Abläufe beim Software-Rollout
|
||||
|
||||
👉 Lösung:
|
||||
|
||||
* automatisierte Deployments
|
||||
|
||||
---
|
||||
|
||||
## 4. ❌ Skalierung schwierig
|
||||
|
||||
Mehr Server = mehr Aufwand
|
||||
|
||||
👉 Lösung:
|
||||
|
||||
* gleiche Playbooks für 1 oder 100 Server
|
||||
|
||||
---
|
||||
|
||||
# ⚙️ Typische Anwendungsfälle (mit Praxisbeispielen)
|
||||
|
||||
## 🧪 Beispiel 1: Webserver aufsetzen
|
||||
|
||||
Statt:
|
||||
|
||||
* SSH einloggen
|
||||
* Pakete installieren
|
||||
* Config schreiben
|
||||
|
||||
👉 Mit Ansible:
|
||||
|
||||
* ein Playbook ausführen → fertig
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Beispiel 2: Continuous Deployment
|
||||
|
||||
In Kombination mit Tools wie:
|
||||
|
||||
* Jenkins
|
||||
* GitLab
|
||||
|
||||
Ablauf:
|
||||
|
||||
1. Code wird gepusht
|
||||
2. Pipeline startet
|
||||
3. Ansible deployed neue Version
|
||||
|
||||
---
|
||||
|
||||
## ☁️ Beispiel 3: Cloud-Setup
|
||||
|
||||
* Server via Terraform erstellen
|
||||
* Ansible installiert:
|
||||
|
||||
* Datenbank
|
||||
* Backend
|
||||
* Monitoring
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Beispiel 4: Updates automatisieren
|
||||
|
||||
```yaml
|
||||
- name: Update alle Server
|
||||
apt:
|
||||
upgrade: dist
|
||||
```
|
||||
|
||||
👉 Ergebnis:
|
||||
|
||||
* alle Systeme sind aktuell – ohne manuelles Eingreifen
|
||||
|
||||
---
|
||||
|
||||
# ⚠️ Herausforderungen und Grenzen
|
||||
|
||||
## 1. 📚 Komplexität bei großen Projekten
|
||||
|
||||
* viele Playbooks → schwer zu überblicken
|
||||
|
||||
👉 Lösung:
|
||||
|
||||
* Rollen & Struktur nutzen
|
||||
|
||||
---
|
||||
|
||||
## 2. 🐢 Performance
|
||||
|
||||
* läuft über SSH → langsamer als agentbasierte Tools
|
||||
|
||||
---
|
||||
|
||||
## 3. 🧪 Debugging
|
||||
|
||||
* Fehleranalyse manchmal schwierig
|
||||
|
||||
---
|
||||
|
||||
## 4. 🧑💻 YAML-Fallen
|
||||
|
||||
* Einrückung kritisch („Whitespace matters“)
|
||||
|
||||
---
|
||||
|
||||
## 5. 🔐 Zugriffsmanagement
|
||||
|
||||
* SSH-Keys und Rechte müssen sauber konfiguriert sein
|
||||
|
||||
---
|
||||
|
||||
# 🧱 Wichtige Konzepte (kurz erklärt)
|
||||
|
||||
* **Inventory** → Liste der Zielsysteme
|
||||
* **Playbook** → Ablaufbeschreibung
|
||||
* **Task** → einzelne Aktion
|
||||
* **Role** → wiederverwendbare Struktur
|
||||
* **Module** → konkrete Funktionen (z. B. apt, copy)
|
||||
|
||||
---
|
||||
|
||||
# 🧭 Wann solltest du Ansible verwenden?
|
||||
|
||||
👉 Gute Wahl, wenn du:
|
||||
|
||||
* viele Server verwalten musst
|
||||
* wiederholbare Deployments brauchst
|
||||
* schnell starten willst (geringe Einstiegshürde)
|
||||
|
||||
👉 Weniger geeignet:
|
||||
|
||||
* bei extrem großen, hochdynamischen Systemen (teilweise bessere Alternativen)
|
||||
|
||||
---
|
||||
|
||||
# 🧠 Fazit
|
||||
|
||||
**Ansible ist eines der zugänglichsten Tools für Automatisierung in der Softwareentwicklung.**
|
||||
|
||||
Es hilft dir:
|
||||
|
||||
* Fehler zu reduzieren
|
||||
* Zeit zu sparen
|
||||
* Infrastruktur reproduzierbar zu machen
|
||||
|
||||
👉 Besonders stark ist es durch:
|
||||
|
||||
* einfache Syntax (YAML)
|
||||
* agentenlose Architektur
|
||||
* breite Einsatzmöglichkeiten
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
|
||||
---
|
||||
|
||||
# 🐳 Einführung in Docker
|
||||
|
||||
## 1. Was ist Docker?
|
||||
|
||||
Docker ist eine Plattform, mit der du **Container** erstellen, starten und verwalten kannst.
|
||||
Ein Container ist eine Art "leichtgewichtige virtuelle Maschine":
|
||||
|
||||
- Enthält **nur das Nötigste** (Programm, Abhängigkeiten, Bibliotheken).
|
||||
- Läuft isoliert, aber nutzt den Kernel des Host-Systems (kein eigener Kernel wie bei einer VM).
|
||||
- Ist **schneller** und **ressourcenschonender** als klassische VMs.
|
||||
|
||||
👉 Bildlich: Statt für jedes Programm einen eigenen PC (VM) aufzubauen, packst du nur das Programm in eine "Transportbox" (Container), die überall läuft, solange Docker installiert ist.
|
||||
|
||||
---
|
||||
|
||||
## 2. Vorteile und typische Anwendungsfälle
|
||||
|
||||
### Vorteile
|
||||
|
||||
- **Portabilität**: „Läuft auf meinem Rechner“ = „läuft auch auf Servern“.
|
||||
- **Isolierung**: Verschiedene Programme/Versionen stören sich nicht.
|
||||
- **Leichtgewicht**: Start in Sekunden statt Minuten wie bei VMs.
|
||||
- **Reproduzierbarkeit**: Gleiche Umgebung für Entwicklung, Test und Produktion.
|
||||
- **Einfache Verteilung**: Images können über Docker Hub oder private Registries geteilt werden.
|
||||
|
||||
### Typische Anwendungsfälle
|
||||
|
||||
- 🚀 **Entwicklung**: Z. B. [[Python]] 3.10 testen, obwohl auf dem System Python 3.12 läuft.
|
||||
- 🗄️ **Datenbanken**: [[MySQL]], PostgreSQL, Redis, MongoDB zum Testen ohne Installation.
|
||||
- 🌐 **Web-Apps**: Schnell mal eine fertige Anwendung ausprobieren (WordPress, Nextcloud, GitLab etc.).
|
||||
- 🧪 **Testing**: Mehrere Umgebungen simulieren (verschiedene OS, Bibliotheksversionen).
|
||||
- 📦 **Deployment**: Gleiche App auf mehreren Servern identisch bereitstellen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Hauptfunktionalitäten von Docker
|
||||
|
||||
- **Docker Engine**: Herzstück, das Container ausführt.
|
||||
- **Docker Images**: Baupläne (Templates) für Container (z. B. Python 3.12 + Flask).
|
||||
- **Docker Containers**: Laufende Instanzen von Images.
|
||||
- **Docker Hub/Registry**: Cloud-Marktplatz für fertige Images.
|
||||
- **Docker Compose**: Orchestrierung mehrerer Container (z. B. App + DB + Cache).
|
||||
|
||||
---
|
||||
|
||||
## 4. Installation unter Linux (Beispiel: Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# 1. Alte Versionen entfernen
|
||||
sudo apt-get remove docker docker-engine docker.io containerd runc
|
||||
|
||||
# 2. Repository vorbereiten
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
|
||||
# Docker GPG-Key hinzufügen
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
|
||||
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# Repository einrichten
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
||||
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# 3. Docker installieren
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# 4. Testen
|
||||
sudo docker run hello-world
|
||||
```
|
||||
|
||||
👉 Danach kannst du optional dich in die `docker`-Gruppe aufnehmen (sonst musst du immer `sudo` schreiben):
|
||||
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
# Abmelden/neu anmelden, damit es wirkt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Erste Schritte in der Praxis
|
||||
|
||||
### Beispiel 1: Linux — Container starten
|
||||
|
||||
```bash
|
||||
# Einen Ubuntu-Container starten (interaktiv)
|
||||
docker run -it ubuntu bash
|
||||
|
||||
# Danach bist du im Container:
|
||||
root@<id>:/# cat /etc/os-release
|
||||
```
|
||||
|
||||
Wenn du rausgehst (`exit`), bleibt der Container gespeichert.
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 2: Linux — Nginx-Webserver starten
|
||||
|
||||
```bash
|
||||
# Nginx starten, Port 8080 weiterleiten
|
||||
docker run -d -p 8080:80 nginx
|
||||
|
||||
# Nun im Browser: http://localhost:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 3: Python im Container
|
||||
|
||||
Nehmen wir an, du willst ein Python-Skript in einer isolierten Umgebung ausführen.
|
||||
|
||||
**hello.py**
|
||||
|
||||
```python
|
||||
print("Hello from Dockerized Python!")
|
||||
```
|
||||
|
||||
**Dockerfile**
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Skript ins Image kopieren
|
||||
COPY hello.py /app/hello.py
|
||||
|
||||
# Arbeitsverzeichnis setzen
|
||||
WORKDIR /app
|
||||
|
||||
# Default-Befehl
|
||||
CMD ["python", "hello.py"]
|
||||
```
|
||||
|
||||
**Bauen und starten**
|
||||
|
||||
```bash
|
||||
# Image bauen
|
||||
docker build -t mypythonapp .
|
||||
|
||||
# Container starten
|
||||
docker run --rm mypythonapp
|
||||
```
|
||||
|
||||
Ausgabe:
|
||||
|
||||
```
|
||||
Hello from Dockerized Python!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Beispiel 4: Python mit externer Bibliothek
|
||||
|
||||
```python
|
||||
# requirements.txt
|
||||
flask==3.0.0
|
||||
```
|
||||
|
||||
**app.py**
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "Hello, Docker!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
```
|
||||
|
||||
**Dockerfile**
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY app.py .
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
```
|
||||
|
||||
**Starten**
|
||||
|
||||
```bash
|
||||
docker build -t flaskapp .
|
||||
docker run -d -p 5000:5000 flaskapp
|
||||
```
|
||||
|
||||
👉 Browser öffnen: `http://localhost:5000`
|
||||
|
||||
---
|
||||
|
||||
## 6. Nützliche Docker-Befehle (Cheatsheet)
|
||||
|
||||
- `docker ps` – laufende Container
|
||||
- `docker ps -a` – alle Container (inkl. gestoppter)
|
||||
- `docker images` – vorhandene Images
|
||||
- `docker stop <id>` – Container stoppen
|
||||
- `docker rm <id>` – Container löschen
|
||||
- `docker rmi <image>` – Image löschen
|
||||
- `docker exec -it <id> bash` – in Container einsteigen
|
||||
|
||||
---
|
||||
|
||||
✅ Damit hast du einen vollständigen Überblick über Docker:
|
||||
|
||||
- **Grundidee**
|
||||
- **Vorteile/Anwendungsfälle**
|
||||
- **Installation**
|
||||
- **Praxis mit Linux und Python**
|
||||
|
||||
---
|
||||
# Anwendungsbeispiel
|
||||
# 🐳 Docker Anwendungsbeispiel - Detaillierte Erklärung für Einsteiger
|
||||
|
||||
## 📚 Was ist Docker überhaupt?
|
||||
|
||||
**Docker** ist wie ein **"Umzugskarton für Software"**:
|
||||
- Stell Dir vor, Du willst Deine App an einen Freund verschicken
|
||||
- Statt nur den Code zu senden, packst Du **alles** ein: Code, Python, alle Bibliotheken, Konfiguration
|
||||
- Dein Freund öffnet den "Karton" (Container) und alles läuft sofort - egal welches Betriebssystem er hat
|
||||
|
||||
---
|
||||
|
||||
## 📁 Projektstruktur mit Erklärungen
|
||||
|
||||
```
|
||||
python-docker-project/
|
||||
├── app/ # 📂 Unser Python-Code
|
||||
│ ├── __init__.py # (macht 'app' zu einem Python-Paket)
|
||||
│ ├── main.py # Hauptprogramm
|
||||
│ ├── data_processor.py # Modul für Datenverarbeitung
|
||||
│ └── database.py # Modul für Datenbankzugriff
|
||||
├── input/ # 📁 Hier liegen die Input-Dateien (vom Host)
|
||||
│ └── sample_data.csv # Beispieldatei
|
||||
├── output/ # 📁 Hier kommen verarbeitete Dateien rein
|
||||
├── requirements.txt # 📝 Liste aller Python-Pakete die wir brauchen
|
||||
├── Dockerfile # 🐳 "Bauanleitung" für unseren Container
|
||||
├── docker-compose.yml # 🎼 "Dirigent" für mehrere Container
|
||||
└── .env # 🔐 Geheime Einstellungen (Passwörter etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐍 Python-Code (wie gewohnt)
|
||||
|
||||
### **requirements.txt** - "Einkaufsliste" für Python-Pakete
|
||||
```txt
|
||||
# 🛒 Diese Pakete braucht unser Programm
|
||||
pandas==2.1.4 # Für Excel/CSV-Verarbeitung
|
||||
psycopg2-binary==2.9.9 # Um mit PostgreSQL zu sprechen
|
||||
python-dotenv==1.0.0 # Um .env-Dateien zu lesen
|
||||
sqlalchemy==2.0.23 # Einfache Datenbankanbindung
|
||||
```
|
||||
|
||||
**💡 Warum brauche ich das?**
|
||||
- Ohne Docker müsstest Du auf jedem PC manuell `pip install pandas` etc. machen
|
||||
- Mit Docker installiert sich das automatisch im Container
|
||||
|
||||
### **app/main.py** - Unser Hauptprogramm
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
🎯 Das macht unser Programm:
|
||||
1. Schaut in /app/input nach CSV-Dateien
|
||||
2. Verarbeitet sie (quadriert Werte, etc.)
|
||||
3. Speichert Ergebnis in PostgreSQL-Datenbank
|
||||
4. Exportiert auch als CSV in /app/output
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from .data_processor import DataProcessor
|
||||
from .database import DatabaseManager
|
||||
|
||||
def main():
|
||||
print("🚀 Starte Data Processing Pipeline...")
|
||||
|
||||
# 📁 Diese Pfade sind INSIDE dem Container!
|
||||
input_dir = Path("/app/input") # Gemountet von Host ./input/
|
||||
output_dir = Path("/app/output") # Gemountet von Host ./output/
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 🗄️ Verbindung zur Datenbank (anderer Container!)
|
||||
db_manager = DatabaseManager()
|
||||
processor = DataProcessor(db_manager)
|
||||
|
||||
# 📊 Alle CSV-Dateien finden und verarbeiten
|
||||
csv_files = list(input_dir.glob("*.csv"))
|
||||
|
||||
if not csv_files:
|
||||
print("❌ Keine CSV-Dateien im Input-Verzeichnis gefunden!")
|
||||
return
|
||||
|
||||
for csv_file in csv_files:
|
||||
print(f"📄 Verarbeite: {csv_file.name}")
|
||||
|
||||
# 1️⃣ Daten aus CSV laden und verarbeiten
|
||||
result_data = processor.process_file(csv_file)
|
||||
|
||||
# 2️⃣ In PostgreSQL-Container speichern
|
||||
processor.save_to_database(result_data, csv_file.stem)
|
||||
|
||||
# 3️⃣ Als neue CSV-Datei exportieren
|
||||
output_file = output_dir / f"processed_{csv_file.name}"
|
||||
result_data.to_csv(output_file, index=False)
|
||||
print(f"💾 Gespeichert als: {output_file}")
|
||||
|
||||
print("✅ Pipeline abgeschlossen!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### **app/database.py** - Spricht mit der Datenbank
|
||||
```python
|
||||
import os
|
||||
import pandas as pd
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
class DatabaseManager:
|
||||
def __init__(self):
|
||||
# 🔌 Diese Werte kommen aus der docker-compose.yml!
|
||||
self.host = os.getenv('DB_HOST', 'postgres') # Name des DB-Containers!
|
||||
self.port = os.getenv('DB_PORT', '5432')
|
||||
self.database = os.getenv('DB_NAME', 'dataprocessing')
|
||||
self.user = os.getenv('DB_USER', 'postgres')
|
||||
self.password = os.getenv('DB_PASSWORD', 'password')
|
||||
|
||||
# 🌐 Connection String - wie eine Adresse zur Datenbank
|
||||
self.connection_string = (
|
||||
f"postgresql://{self.user}:{self.password}@"
|
||||
f"{self.host}:{self.port}/{self.database}"
|
||||
)
|
||||
|
||||
self.engine = None
|
||||
self._connect()
|
||||
|
||||
def _connect(self):
|
||||
"""Verbindung zur Datenbank herstellen"""
|
||||
try:
|
||||
self.engine = create_engine(self.connection_string)
|
||||
# 🏓 Kurzer Test: "Hallo Datenbank!"
|
||||
with self.engine.connect() as conn:
|
||||
conn.execute(text("SELECT 1"))
|
||||
print("✅ Datenbankverbindung erfolgreich")
|
||||
except Exception as e:
|
||||
print(f"❌ Datenbankverbindung fehlgeschlagen: {e}")
|
||||
raise
|
||||
|
||||
def save_dataframe(self, df: pd.DataFrame, table_name: str):
|
||||
"""Speichert DataFrame als Tabelle in PostgreSQL"""
|
||||
df.to_sql(
|
||||
name=table_name,
|
||||
con=self.engine,
|
||||
if_exists='replace', # Überschreibt existierende Tabelle
|
||||
index=False
|
||||
)
|
||||
```
|
||||
|
||||
*(Die anderen Python-Dateien bleiben gleich)*
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Dockerfile - "Bauanleitung" für unseren Container
|
||||
|
||||
```dockerfile
|
||||
# 🏗️ SCHRITT 1: Basis-Image wählen
|
||||
# Das ist wie ein "Rohbau" - Linux + Python sind schon installiert
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 🛠️ SCHRITT 2: System-Tools installieren
|
||||
# Wir brauchen gcc und libpq-dev für PostgreSQL-Verbindung
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
libpq-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 💡 WARUM: psycopg2 (PostgreSQL-Driver) braucht diese Tools zum Kompilieren
|
||||
|
||||
# 📂 SCHRITT 3: Arbeitsverzeichnis setzen
|
||||
WORKDIR /app
|
||||
# Das ist wie "cd /app" - alle folgenden Befehle laufen hier
|
||||
|
||||
# 📦 SCHRITT 4: Python-Pakete installieren
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
# 💡 WARUM separat? Docker cached diesen Layer - muss nur neu gebaut werden wenn requirements.txt ändert
|
||||
|
||||
# 📋 SCHRITT 5: Unseren Code ins Container kopieren
|
||||
COPY app/ ./app/
|
||||
# Kopiert alles aus Host-Ordner "app/" nach Container "/app/app/"
|
||||
|
||||
# 📁 SCHRITT 6: Verzeichnisse erstellen
|
||||
RUN mkdir -p /app/input /app/output
|
||||
# Diese werden später durch Volume-Mounts "überschrieben"
|
||||
|
||||
# ⚙️ SCHRITT 7: Umgebungsvariablen setzen
|
||||
ENV PYTHONPATH=/app # Python findet unsere Module
|
||||
ENV PYTHONUNBUFFERED=1 # Print-Ausgaben erscheinen sofort
|
||||
|
||||
# 🚀 SCHRITT 8: Was soll beim Container-Start passieren?
|
||||
CMD ["python", "-m", "app.main"]
|
||||
# Startet unser Hauptprogramm
|
||||
```
|
||||
|
||||
**🤔 Was passiert beim `docker build`?**
|
||||
1. Docker lädt `python:3.11-slim` herunter (falls nicht da)
|
||||
2. Installiert gcc und PostgreSQL-Tools
|
||||
3. Kopiert `requirements.txt` und installiert Python-Pakete
|
||||
4. Kopiert unseren Code rein
|
||||
5. Setzt Umgebungsvariablen
|
||||
6. **Ergebnis:** Ein fertiger Container mit allem was wir brauchen!
|
||||
|
||||
---
|
||||
|
||||
## 🎼 docker-compose.yml - "Dirigent" für mehrere Container
|
||||
|
||||
```yaml
|
||||
# 🎯 docker-compose orchestriert MEHRERE Container gleichzeitig
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# 🗄️ CONTAINER 1: PostgreSQL-Datenbank
|
||||
postgres:
|
||||
image: postgres:15 # Fertiges PostgreSQL-Image von Docker Hub
|
||||
environment: # Umgebungsvariablen für PostgreSQL
|
||||
POSTGRES_DB: dataprocessing # Name der Datenbank die erstellt wird
|
||||
POSTGRES_USER: postgres # Benutzername
|
||||
POSTGRES_PASSWORD: password # 🔐 Passwort (in Produktion geheimer!)
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data # 💾 Daten persistent speichern
|
||||
- ./db_init:/docker-entrypoint-initdb.d # 📂 Optional: SQL-Skripts beim Start
|
||||
ports:
|
||||
- "5432:5432" # Port freigeben (Host:Container)
|
||||
networks:
|
||||
- app_network # 🌐 Internes Netzwerk für Container-Kommunikation
|
||||
|
||||
# 🐍 CONTAINER 2: Unsere Python-Anwendung
|
||||
python_app:
|
||||
build: . # 🏗️ Baue Container aus Dockerfile in diesem Verzeichnis
|
||||
environment: # Diese Variablen kann unser Python-Code lesen
|
||||
DB_HOST: postgres # ❗ WICHTIG: Name des DB-Containers (nicht localhost!)
|
||||
DB_PORT: 5432
|
||||
DB_NAME: dataprocessing
|
||||
DB_USER: postgres
|
||||
DB_PASSWORD: password
|
||||
volumes: # 📂 Verbinde Host-Verzeichnisse mit Container
|
||||
- ./input:/app/input:ro # Host ./input → Container /app/input (read-only)
|
||||
- ./output:/app/output # Host ./output → Container /app/output (read-write)
|
||||
depends_on: # ⏱️ Warte bis postgres-Container läuft
|
||||
- postgres
|
||||
networks:
|
||||
- app_network # 🌐 Gleiches Netzwerk = können miteinander sprechen
|
||||
restart: unless-stopped # 🔄 Automatisch neu starten bei Absturz
|
||||
|
||||
# 🌐 CONTAINER 3: pgAdmin (Web-Interface für PostgreSQL)
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:8
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||
PGADMIN_DEFAULT_PASSWORD: admin
|
||||
ports:
|
||||
- "8080:80" # 🌐 Zugriff via http://localhost:8080
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- app_network
|
||||
|
||||
# 💾 VOLUMES: Persistente Datenspeicherung
|
||||
volumes:
|
||||
postgres_data: # Docker verwaltet diesen Speicher automatisch
|
||||
# 💡 Auch wenn Container gelöscht wird, bleiben Daten erhalten
|
||||
|
||||
# 🌐 NETWORKS: Virtuelle Netzwerke für Container-Kommunikation
|
||||
networks:
|
||||
app_network:
|
||||
driver: bridge # Standard-Netzwerk-Typ
|
||||
# 💡 Alle Container in diesem Netzwerk können sich über Namen erreichen
|
||||
```
|
||||
|
||||
**🤔 Warum docker-compose?**
|
||||
- **Ohne compose:** `docker run postgres`, dann `docker run python_app` - sehr umständlich!
|
||||
- **Mit compose:** Ein Befehl (`docker-compose up`) startet ALLES automatisch
|
||||
- **Networks:** Container können sich über Namen finden (`postgres` statt IP-Adresse)
|
||||
- **Volumes:** Daten gehen nicht verloren beim Container-Neustart
|
||||
|
||||
---
|
||||
|
||||
## 🔧 .env - Geheime Konfiguration
|
||||
|
||||
```env
|
||||
# 🔐 Sensible Daten sollten NICHT in docker-compose.yml stehen
|
||||
# Diese Datei wird von docker-compose automatisch gelesen
|
||||
|
||||
# Datenbankverbindung
|
||||
DB_HOST=postgres
|
||||
DB_PORT=5432
|
||||
DB_NAME=dataprocessing
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=password # ❗ In Produktion: sicheres Passwort!
|
||||
|
||||
# Python-Einstellungen
|
||||
PYTHONPATH=/app
|
||||
PYTHONUNBUFFERED=1
|
||||
```
|
||||
|
||||
**💡 Wichtig:** Die `.env`-Datei niemals in Git committen! (→ `.gitignore`)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Beispiel-Daten
|
||||
|
||||
### **input/sample_data.csv**
|
||||
```csv
|
||||
id,name,value,category
|
||||
1,Item A,10,electronics
|
||||
2,Item B,25,clothing
|
||||
3,Item C,15,electronics
|
||||
4,Item D,30,books
|
||||
5,Item E,8,clothing
|
||||
```
|
||||
|
||||
**💡 Das ist eine normale CSV-Datei auf Deinem Host-PC**
|
||||
- Liegt in `./input/sample_data.csv`
|
||||
- Wird in den Container als `/app/input/sample_data.csv` "gemountet"
|
||||
- Unser Python-Code kann sie lesen, als wäre sie im Container
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Schritt-für-Schritt Anleitung
|
||||
|
||||
### **1. Projekt erstellen**
|
||||
```bash
|
||||
# Verzeichnis erstellen
|
||||
mkdir python-docker-project
|
||||
cd python-docker-project
|
||||
|
||||
# Struktur erstellen
|
||||
mkdir -p app input output
|
||||
touch requirements.txt Dockerfile docker-compose.yml .env
|
||||
touch app/__init__.py app/main.py app/data_processor.py app/database.py
|
||||
```
|
||||
|
||||
### **2. Code schreiben**
|
||||
- Alle Dateien wie oben gezeigt erstellen und befüllen
|
||||
|
||||
### **3. Container bauen und starten**
|
||||
```bash
|
||||
# Alle Container bauen und starten
|
||||
docker-compose up --build
|
||||
|
||||
# 🔍 Was passiert:
|
||||
# 1. Docker baut unseren Python-Container (aus Dockerfile)
|
||||
# 2. Lädt PostgreSQL-Image herunter
|
||||
# 3. Lädt pgAdmin-Image herunter
|
||||
# 4. Erstellt Netzwerk "app_network"
|
||||
# 5. Startet postgres-Container
|
||||
# 6. Startet python_app-Container (wartet auf postgres)
|
||||
# 7. Startet pgAdmin-Container
|
||||
```
|
||||
|
||||
### **4. Überprüfen ob alles läuft**
|
||||
```bash
|
||||
# Laufende Container anzeigen
|
||||
docker-compose ps
|
||||
|
||||
# Logs ansehen
|
||||
docker-compose logs python_app
|
||||
docker-compose logs postgres
|
||||
|
||||
# In pgAdmin einloggen: http://localhost:8080
|
||||
# Email: admin@example.com, Passwort: admin
|
||||
```
|
||||
|
||||
### **5. Neue Daten verarbeiten**
|
||||
```bash
|
||||
# Neue CSV-Datei ins input-Verzeichnis kopieren
|
||||
cp neue_daten.csv input/
|
||||
|
||||
# Python-App neu starten um neue Dateien zu verarbeiten
|
||||
docker-compose restart python_app
|
||||
|
||||
# Ergebnis ansehen
|
||||
ls -la output/
|
||||
```
|
||||
|
||||
### **6. System stoppen**
|
||||
```bash
|
||||
# Alle Container stoppen
|
||||
docker-compose down
|
||||
|
||||
# Container stoppen UND Daten löschen (Vorsicht!)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Docker-Befehle erklärt
|
||||
|
||||
### **Grundlegende Container-Befehle**
|
||||
```bash
|
||||
# 📋 Alle laufenden Container anzeigen
|
||||
docker ps
|
||||
|
||||
# 📋 Alle Container anzeigen (auch gestoppte)
|
||||
docker ps -a
|
||||
|
||||
# 🔍 Container-Logs ansehen
|
||||
docker logs <container-name>
|
||||
|
||||
# 🔑 In laufenden Container "einsteigen"
|
||||
docker exec -it <container-name> bash
|
||||
|
||||
# 🗑️ Container stoppen
|
||||
docker stop <container-name>
|
||||
|
||||
# 🗑️ Container löschen
|
||||
docker rm <container-name>
|
||||
```
|
||||
|
||||
### **Image-Befehle**
|
||||
```bash
|
||||
# 📋 Alle Images anzeigen
|
||||
docker images
|
||||
|
||||
# 🏗️ Image aus Dockerfile bauen
|
||||
docker build -t mein-python-app .
|
||||
|
||||
# 🗑️ Image löschen
|
||||
docker rmi <image-name>
|
||||
|
||||
# 🧹 Unbenutzte Images löschen
|
||||
docker image prune
|
||||
```
|
||||
|
||||
### **docker-compose-Befehle**
|
||||
```bash
|
||||
# 🚀 Alle Services starten
|
||||
docker-compose up
|
||||
|
||||
# 🚀 Im Hintergrund starten
|
||||
docker-compose up -d
|
||||
|
||||
# 🏗️ Images neu bauen und starten
|
||||
docker-compose up --build
|
||||
|
||||
# 🔄 Einzelnen Service neu starten
|
||||
docker-compose restart python_app
|
||||
|
||||
# 📋 Status aller Services
|
||||
docker-compose ps
|
||||
|
||||
# 🔍 Logs aller Services
|
||||
docker-compose logs
|
||||
|
||||
# 🔍 Logs eines bestimmten Service
|
||||
docker-compose logs python_app
|
||||
|
||||
# 📈 Live-Logs verfolgen
|
||||
docker-compose logs -f python_app
|
||||
|
||||
# 🔑 In Container einsteigen
|
||||
docker-compose exec python_app bash
|
||||
docker-compose exec postgres psql -U postgres -d dataprocessing
|
||||
|
||||
# ⏹️ Alle Services stoppen
|
||||
docker-compose down
|
||||
|
||||
# 🗑️ Services stoppen und Volumes löschen
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Die wichtigsten Docker-Konzepte
|
||||
|
||||
### **1. Images vs Container**
|
||||
- **Image** = "Bauplan" (wie ein Kuchenrezept)
|
||||
- **Container** = "Laufende Instanz" (wie ein gebackener Kuchen)
|
||||
- Ein Image kann viele Container erstellen
|
||||
|
||||
### **2. Volumes**
|
||||
```yaml
|
||||
volumes:
|
||||
- ./input:/app/input # Host-Verzeichnis → Container-Verzeichnis
|
||||
```
|
||||
- **Warum?** Container-Dateisystem ist temporär
|
||||
- **Volume** = persistenter Speicher der überlebt
|
||||
|
||||
### **3. Networks**
|
||||
- Container in gleichem Netzwerk können sich über Namen erreichen
|
||||
- `python_app` kann `postgres` kontaktieren (nicht IP-Adresse!)
|
||||
|
||||
### **4. Environment Variables**
|
||||
```yaml
|
||||
environment:
|
||||
DB_HOST: postgres # Diese Variable kann Python-Code lesen
|
||||
```
|
||||
|
||||
### **5. Ports**
|
||||
```yaml
|
||||
ports:
|
||||
- "8080:80" # Host-Port : Container-Port
|
||||
```
|
||||
- Container-Port 80 wird auf Host-Port 8080 freigegeben
|
||||
- Zugriff: `http://localhost:8080`
|
||||
|
||||
---
|
||||
|
||||
## ❓ Häufige Anfänger-Probleme
|
||||
|
||||
### **Problem 1: "Connection refused" zur Datenbank**
|
||||
```
|
||||
❌ FALSCH: DB_HOST=localhost
|
||||
✅ RICHTIG: DB_HOST=postgres
|
||||
```
|
||||
- In Docker ist `localhost` der Container selbst
|
||||
- Verwende den Service-Namen aus `docker-compose.yml`
|
||||
|
||||
### **Problem 2: "File not found"**
|
||||
```
|
||||
❌ FALSCH: input_dir = Path("./input")
|
||||
✅ RICHTIG: input_dir = Path("/app/input")
|
||||
```
|
||||
- Pfade sind INSIDE dem Container, nicht auf dem Host
|
||||
|
||||
### **Problem 3: "Module not found"**
|
||||
```
|
||||
❌ FALSCH: Umgebungsvariable vergessen
|
||||
✅ RICHTIG: ENV PYTHONPATH=/app in Dockerfile
|
||||
```
|
||||
|
||||
### **Problem 4: Änderungen nicht sichtbar**
|
||||
```bash
|
||||
# Nach Code-Änderungen Container neu bauen:
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
### **Problem 5: Port bereits belegt**
|
||||
```
|
||||
❌ Error: Port 5432 already in use
|
||||
✅ Lösung: Anderen Port verwenden: "5433:5432"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Zusammenfassung - Was macht was?
|
||||
|
||||
| Datei/Komponente | Was es macht | Warum wichtig |
|
||||
|------------------|--------------|---------------|
|
||||
| **Dockerfile** | "Bauanleitung" für Python-Container | Definiert was IN den Container kommt |
|
||||
| **docker-compose.yml** | Orchestriert mehrere Container | Ein Befehl startet alles |
|
||||
| **requirements.txt** | Liste der Python-Pakete | Automatische Installation |
|
||||
| **.env** | Geheime Konfiguration | Passwörter nicht in Code |
|
||||
| **volumes** | Datenaustausch Host ↔ Container | Input/Output-Dateien |
|
||||
| **networks** | Container-Kommunikation | python_app kann postgres erreichen |
|
||||
| **ports** | Zugriff von außen | Web-Interface erreichbar |
|
||||
|
||||
**🚀 Mit einem Befehl hast Du:**
|
||||
- ✅ Python-Umgebung mit allen Paketen
|
||||
- ✅ PostgreSQL-Datenbank
|
||||
- ✅ Web-Interface für DB
|
||||
- ✅ Automatische File-Verarbeitung
|
||||
- ✅ Persistente Datenspeicherung
|
||||
|
||||
**Das ist die Macht von Docker! 🐳**
|
||||
@@ -0,0 +1,330 @@
|
||||
# 🧠 1. Grundsätzliche Definition
|
||||
|
||||
**Gunicorn** (Green Unicorn) ist ein **Python-basierter HTTP-Server**, der das **WSGI-Protokoll (Web Server Gateway Interface)** implementiert. ([Wikipedia][1])
|
||||
|
||||
👉 Vereinfacht gesagt:
|
||||
|
||||
> Gunicorn ist die **Laufzeitumgebung**, die deine Python-Webanwendung (z. B. Flask/Django) tatsächlich **für HTTP-Anfragen erreichbar macht**.
|
||||
|
||||
### Rolle im System
|
||||
|
||||
```
|
||||
Browser → Webserver (z. B. Nginx) → Gunicorn → Python App (Flask/Django)
|
||||
```
|
||||
|
||||
* Browser sendet HTTP-Request
|
||||
* Gunicorn nimmt ihn entgegen
|
||||
* Gunicorn ruft deine Python-App auf
|
||||
* Response wird zurückgegeben
|
||||
|
||||
➡️ Gunicorn fungiert also als **Brücke zwischen Webserver und Python-Code** ([backendmesh][2])
|
||||
|
||||
---
|
||||
|
||||
# 🔍 2. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
Gunicorn wird oft verwechselt mit anderen Komponenten. Hier die klare Einordnung:
|
||||
|
||||
## 🔹 Gunicorn vs. Webserver (z. B. Nginx, Apache)
|
||||
|
||||
| Komponente | Aufgabe |
|
||||
| ----------------- | ----------------------------------------------------------- |
|
||||
| Webserver (Nginx) | Liefert statische Inhalte (HTML, CSS, Bilder), SSL, Routing |
|
||||
| Gunicorn | Führt Python-Code aus |
|
||||
|
||||
👉 Wichtig:
|
||||
Gunicorn ist **kein vollständiger Webserver**, sondern ein **Application Server**.
|
||||
|
||||
➡️ Deshalb wird häufig kombiniert:
|
||||
|
||||
* Nginx → Reverse Proxy
|
||||
* Gunicorn → Python-Ausführung ([gunicorn.org][3])
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Gunicorn vs. WSGI
|
||||
|
||||
* **WSGI** = Standard (Schnittstelle)
|
||||
* **Gunicorn** = konkrete Implementierung dieses Standards
|
||||
|
||||
👉 Analogie:
|
||||
|
||||
* WSGI = Steckdose
|
||||
* Gunicorn = konkretes Gerät, das man einsteckt
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Gunicorn vs. andere Server
|
||||
|
||||
| Tool | Typ | Besonderheit |
|
||||
| -------- | ---- | --------------------- |
|
||||
| Gunicorn | WSGI | klassisch, stabil |
|
||||
| uWSGI | WSGI | sehr mächtig, komplex |
|
||||
| Uvicorn | ASGI | für async (FastAPI) |
|
||||
| Daphne | ASGI | Channels / WebSockets |
|
||||
|
||||
👉 Abgrenzung:
|
||||
|
||||
* Gunicorn = eher **synchron / klassisch**
|
||||
* Uvicorn & Co = **async-first**
|
||||
|
||||
---
|
||||
|
||||
# 🧩 3. Welche Probleme löst Gunicorn?
|
||||
|
||||
## ❌ Problem 1: Python kann nicht direkt HTTP sprechen
|
||||
|
||||
Python-Apps (z. B. Flask) sind **keine Webserver**.
|
||||
|
||||
👉 Gunicorn löst:
|
||||
|
||||
* HTTP parsing
|
||||
* Request-Handling
|
||||
* Verbindung zu Python-Code
|
||||
|
||||
---
|
||||
|
||||
## ❌ Problem 2: Entwicklungsserver ist ungeeignet für Produktion
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
flask run
|
||||
```
|
||||
|
||||
➡️ Nachteile:
|
||||
|
||||
* langsam
|
||||
* unsicher
|
||||
* nicht skalierbar
|
||||
|
||||
👉 Gunicorn bietet:
|
||||
|
||||
* stabile Produktionsumgebung
|
||||
* parallele Verarbeitung
|
||||
* Fehlerisolierung
|
||||
|
||||
---
|
||||
|
||||
## ❌ Problem 3: Skalierung / Parallelität
|
||||
|
||||
Gunicorn nutzt ein **Pre-Fork Worker-Modell**:
|
||||
|
||||
* Master-Prozess startet mehrere Worker
|
||||
* Jeder Worker verarbeitet Requests unabhängig ([Wikipedia][1])
|
||||
|
||||
👉 Vorteil:
|
||||
|
||||
* bessere CPU-Auslastung
|
||||
* stabil bei Lastspitzen ([gunicorn.org][4])
|
||||
|
||||
---
|
||||
|
||||
## ❌ Problem 4: Standardisierung (WSGI)
|
||||
|
||||
Ohne WSGI:
|
||||
|
||||
* jedes Framework anders
|
||||
|
||||
Mit Gunicorn:
|
||||
|
||||
* einheitliche Schnittstelle für:
|
||||
|
||||
* Django
|
||||
* Flask
|
||||
* FastAPI (WSGI/ASGI-kompatibel)
|
||||
|
||||
---
|
||||
|
||||
# ⚙️ 4. Architektur & Funktionsweise
|
||||
|
||||
## 🧱 Pre-Fork-Modell
|
||||
|
||||
* 1 Master-Prozess
|
||||
* N Worker-Prozesse
|
||||
|
||||
```
|
||||
Master
|
||||
/ | \
|
||||
Worker Worker Worker
|
||||
```
|
||||
|
||||
👉 Vorteile:
|
||||
|
||||
* Isolation (Crash betrifft nur einen Worker)
|
||||
* gute Parallelisierung
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Worker-Typen
|
||||
|
||||
Gunicorn bietet mehrere Modelle:
|
||||
|
||||
| Typ | Einsatz |
|
||||
| ----------------------- | -------------------------------- |
|
||||
| sync | einfache APIs |
|
||||
| async (gevent/eventlet) | viele gleichzeitige Verbindungen |
|
||||
| threads | Mischung |
|
||||
| asyncio | moderne async Apps |
|
||||
|
||||
---
|
||||
|
||||
# ⚠️ 5. Herausforderungen & Nachteile
|
||||
|
||||
## ❗ 1. Kein vollwertiger Webserver
|
||||
|
||||
Gunicorn kann nicht gut:
|
||||
|
||||
* statische Dateien ausliefern
|
||||
* SSL terminieren
|
||||
|
||||
👉 Lösung:
|
||||
→ Kombination mit Nginx
|
||||
|
||||
---
|
||||
|
||||
## ❗ 2. Konfiguration nicht trivial
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
gunicorn -w 4 -k gevent app:app
|
||||
```
|
||||
|
||||
Fragen:
|
||||
|
||||
* wie viele Worker?
|
||||
* sync vs async?
|
||||
* Threads?
|
||||
|
||||
👉 falsche Wahl = schlechte Performance
|
||||
|
||||
---
|
||||
|
||||
## ❗ 3. Async-Limitierungen
|
||||
|
||||
* Gunicorn ist historisch WSGI (synchron)
|
||||
* Async nur über Erweiterungen
|
||||
|
||||
👉 Alternative:
|
||||
→ Uvicorn / Hypercorn für moderne Apps
|
||||
|
||||
---
|
||||
|
||||
## ❗ 4. Ressourcenverbrauch
|
||||
|
||||
* Jeder Worker = eigener Prozess
|
||||
* hoher RAM-Verbrauch bei vielen Workern
|
||||
|
||||
---
|
||||
|
||||
## ❗ 5. Debugging schwieriger
|
||||
|
||||
* mehrere Prozesse
|
||||
* Race Conditions möglich
|
||||
|
||||
---
|
||||
|
||||
# 💡 6. Praxisnahe Beispiele
|
||||
|
||||
## 🧪 Beispiel 1: Flask lokal vs. Produktion
|
||||
|
||||
### Entwicklung
|
||||
|
||||
```bash
|
||||
flask run
|
||||
```
|
||||
|
||||
### Produktion
|
||||
|
||||
```bash
|
||||
gunicorn -w 4 app:app
|
||||
```
|
||||
|
||||
👉 Effekt:
|
||||
|
||||
* 4 parallele Worker
|
||||
* deutlich höhere Stabilität
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Beispiel 2: Typisches Deployment
|
||||
|
||||
```text
|
||||
Internet
|
||||
↓
|
||||
Nginx (SSL, Static Files)
|
||||
↓
|
||||
Gunicorn (Python Server)
|
||||
↓
|
||||
Django App
|
||||
```
|
||||
|
||||
👉 Vorteile:
|
||||
|
||||
* Nginx: schnell für statische Inhalte
|
||||
* Gunicorn: spezialisiert auf Python
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Beispiel 3: Skalierung
|
||||
|
||||
Traffic steigt → Anpassung:
|
||||
|
||||
```bash
|
||||
gunicorn -w 8 app:app
|
||||
```
|
||||
|
||||
👉 Mehr Worker = mehr parallele Requests
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Beispiel 4: Async API (z. B. Chat-App)
|
||||
|
||||
Problem:
|
||||
|
||||
* viele offene Verbindungen
|
||||
|
||||
Lösung:
|
||||
|
||||
```bash
|
||||
gunicorn -k gevent -w 4 app:app
|
||||
```
|
||||
|
||||
👉 async Worker verarbeitet tausende Verbindungen
|
||||
|
||||
---
|
||||
|
||||
# 🧭 7. Wann nutzt man Gunicorn?
|
||||
|
||||
✅ Typische Einsatzfälle:
|
||||
|
||||
* Django / Flask Apps
|
||||
* klassische REST APIs
|
||||
* Container (Docker)
|
||||
* Linux-Server
|
||||
|
||||
❌ Weniger geeignet:
|
||||
|
||||
* hochgradig async Systeme (→ Uvicorn)
|
||||
* statische Seiten (→ Nginx allein)
|
||||
|
||||
---
|
||||
|
||||
# 🧾 Fazit
|
||||
|
||||
Gunicorn ist ein **zentraler Baustein moderner Python-Webanwendungen**:
|
||||
|
||||
👉 Kurz gesagt:
|
||||
|
||||
* **Definition:** WSGI HTTP Server für Python
|
||||
* **Rolle:** verbindet Webserver mit Python-Code
|
||||
* **Stärken:** einfach, stabil, production-ready
|
||||
* **Schwächen:** begrenztes Async, braucht oft Nginx
|
||||
|
||||
---
|
||||
|
||||
[1]: https://en.wikipedia.org/wiki/Gunicorn?utm_source=chatgpt.com "Gunicorn"
|
||||
[2]: https://www.backendmesh.com/gunicorn-python-wsgi-http-server/?utm_source=chatgpt.com "A Detailed Overview of Gunicorn: Python WSGI HTTP Server - Backendmesh"
|
||||
[3]: https://gunicorn.org/index.html?utm_source=chatgpt.com "Gunicorn - Python WSGI HTTP Server for UNIX"
|
||||
[4]: https://gunicorn.org/?utm_source=chatgpt.com "Gunicorn - Python WSGI HTTP Server for UNIX"
|
||||
@@ -0,0 +1 @@
|
||||
https://www.librechat.ai/
|
||||
@@ -0,0 +1,207 @@
|
||||
|
||||
|
||||
**Open WebUI is an [extensible](https://docs.openwebui.com/features/plugin/), feature-rich, and user-friendly self-hosted AI platform designed to operate entirely offline.** It supports various LLM runners like **Ollama** and **OpenAI-compatible APIs**, with **built-in inference engine** for RAG, making it a **powerful AI deployment solution**.
|
||||
|
||||
Passionate about open-source AI? [Join our team →](https://careers.openwebui.com/)
|
||||
|
||||
       [](https://discord.gg/5rJgQTnV4s) [](https://github.com/sponsors/tjbck)
|
||||
|
||||

|
||||
|
||||
tip
|
||||
|
||||
**Looking for an [Enterprise Plan](https://docs.openwebui.com/enterprise)?** — **[Speak with Our Sales Team Today!](mailto:sales@openwebui.com)**
|
||||
|
||||
Get **enhanced capabilities**, including **custom theming and branding**, **Service Level Agreement (SLA) support**, **Long-Term Support (LTS) versions**, and **more!**
|
||||
|
||||
## Quick Start with [[Docker]] 🐳
|
||||
|
||||
info
|
||||
|
||||
**WebSocket** support is required for Open WebUI to function correctly. Ensure that your network configuration allows WebSocket connections.
|
||||
|
||||
**If Ollama is on your computer**, use this command:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
|
||||
```
|
||||
|
||||
**To run Open WebUI with Nvidia GPU support**, use this command:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
|
||||
```
|
||||
|
||||
For environments with limited storage or bandwidth, Open WebUI offers slim image variants that exclude pre-bundled models. These images are significantly smaller but download required models on first use:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main-slim
|
||||
```
|
||||
|
||||
### Open WebUI Bundled with Ollama
|
||||
|
||||
This installation method uses a single container image that bundles Open WebUI with Ollama, allowing for a streamlined setup via a single command. Choose the appropriate command based on your hardware setup:
|
||||
|
||||
- **With GPU Support**: Utilize GPU resources by running the following command:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 --gpus=all -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama
|
||||
```
|
||||
|
||||
|
||||
- **For CPU Only**: If you're not using a GPU, use this command instead:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama
|
||||
```
|
||||
|
||||
|
||||
Both commands facilitate a built-in, hassle-free installation of both Open WebUI and Ollama, ensuring that you can get everything up and running swiftly.
|
||||
|
||||
After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
|
||||
|
||||
### Using the Dev Branch 🌙
|
||||
|
||||
warning
|
||||
|
||||
The `:dev` branch contains the latest unstable features and changes. Use it at your own risk as it may have bugs or incomplete features.
|
||||
|
||||
If you want to try out the latest bleeding-edge features and are okay with occasional instability, you can use the `:dev` tag like this:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:dev
|
||||
```
|
||||
|
||||
For the slim variant of the dev branch:
|
||||
|
||||
```
|
||||
docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:dev-slim
|
||||
```
|
||||
|
||||
### Updating Open WebUI
|
||||
|
||||
To update Open WebUI container easily, follow these steps:
|
||||
|
||||
#### Manual Update
|
||||
|
||||
Use [Watchtower](https://containrrr.dev/watchtower) to update your Docker container manually:
|
||||
|
||||
```
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --run-once open-webui
|
||||
```
|
||||
|
||||
#### Automatic Updates
|
||||
|
||||
Keep your container updated automatically every 5 minutes:
|
||||
|
||||
```
|
||||
docker run -d --name watchtower --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --interval 300 open-webui
|
||||
```
|
||||
|
||||
🔧 **Note**: Replace `open-webui` with your container name if it's different.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
info
|
||||
|
||||
### Platform Compatibility
|
||||
|
||||
Open WebUI works on macOS, [[Linux]] (x86_64 and ARM64, including Raspberry Pi and other ARM boards), and Windows.
|
||||
|
||||
There are two main ways to install and run Open WebUI: using the `uv` runtime manager or Python's `pip`. While both methods are effective, **we strongly recommend using `uv`** as it simplifies environment management and minimizes potential conflicts.
|
||||
|
||||
### Installation with `uv` (Recommended)
|
||||
|
||||
The `uv` runtime manager ensures seamless Python environment management for applications like Open WebUI. Follow these steps to get started:
|
||||
|
||||
#### 1. Install `uv`
|
||||
|
||||
Pick the appropriate installation command for your operating system:
|
||||
|
||||
- **macOS/Linux**:
|
||||
|
||||
```
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
|
||||
- **Windows**:
|
||||
|
||||
```
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
|
||||
#### 2. Run Open WebUI
|
||||
|
||||
Once `uv` is installed, running Open WebUI is a breeze. Use the command below, ensuring to set the `DATA_DIR` environment variable to avoid data loss. Example paths are provided for each platform:
|
||||
|
||||
- **macOS/Linux**:
|
||||
|
||||
```
|
||||
DATA_DIR=~/.open-webui uvx --python 3.11 open-webui@latest serve
|
||||
```
|
||||
|
||||
|
||||
- **Windows**:
|
||||
|
||||
```
|
||||
$env:DATA_DIR="C:\open-webui\data"; uvx --python 3.11 open-webui@latest serve
|
||||
```
|
||||
|
||||
|
||||
note
|
||||
|
||||
**For PostgreSQL Support:**
|
||||
|
||||
The default installation now uses a slimmed-down package. If you need **PostgreSQL support**, install with all optional dependencies:
|
||||
|
||||
```
|
||||
pip install open-webui[all]
|
||||
```
|
||||
|
||||
### Installation with `pip`
|
||||
|
||||
For users installing Open WebUI with [[Python]]'s package manager `pip`, **it is strongly recommended to use Python runtime managers like `uv` or `conda`**. These tools help manage Python environments effectively and avoid conflicts.
|
||||
|
||||
Python 3.11 is the development environment. Python 3.12 seems to work but has not been thoroughly tested. Python 3.13 is entirely untested and some dependencies do not work with Python 3.13 yet—**use at your own risk**.
|
||||
|
||||
1. **Install Open WebUI**:
|
||||
|
||||
Open your terminal and run the following command:
|
||||
|
||||
```
|
||||
pip install open-webui
|
||||
```
|
||||
|
||||
|
||||
- **Start Open WebUI**:
|
||||
|
||||
Once installed, start the server using:
|
||||
|
||||
```
|
||||
open-webui serve
|
||||
```
|
||||
|
||||
|
||||
### Updating Open WebUI
|
||||
|
||||
To update to the latest version, simply run:
|
||||
|
||||
```
|
||||
pip install --upgrade open-webui
|
||||
```
|
||||
|
||||
This method installs all necessary dependencies and starts Open WebUI, allowing for a simple and efficient setup. After installation, you can access Open WebUI at [http://localhost:8080](http://localhost:8080). Enjoy! 😄
|
||||
|
||||
## Other Installation Methods
|
||||
|
||||
We offer various installation alternatives, including non-Docker native installation methods, Docker Compose, Kustomize, and Helm. Visit our [Open WebUI Documentation](https://docs.openwebui.com/getting-started/) or join our [Discord community](https://discord.gg/5rJgQTnV4s) for comprehensive guidance.
|
||||
|
||||
Continue with the full [getting started guide](https://docs.openwebui.com/getting-started).
|
||||
|
||||
### Desktop App
|
||||
|
||||
We also have an **experimental** desktop app, which is actively a **work in progress (WIP)**. While it offers a convenient way to run Open WebUI natively on your system without Docker or manual setup, it is **not yet stable**.
|
||||
|
||||
👉 For stability and production use, we strongly recommend installing via **Docker** or **Python (`uv` or `pip`)**.
|
||||
@@ -0,0 +1,319 @@
|
||||
# 🧠 1. Grundsätzliche Definition
|
||||
|
||||
**Hypercorn** ist ein **Python-Webserver**, genauer gesagt ein sogenannter **ASGI-Server** (und optional auch WSGI-Server).
|
||||
|
||||
👉 Kurz gesagt:
|
||||
|
||||
> Hypercorn ist die Software, die deine Webanwendung tatsächlich „ins Internet bringt“ und HTTP-Anfragen verarbeitet.
|
||||
|
||||
Technisch:
|
||||
|
||||
* implementiert die **ASGI-Spezifikation (Asynchronous Server Gateway Interface)**
|
||||
* kann **HTTP/1.1, HTTP/2, WebSockets und sogar HTTP/3** bedienen ([PyPI][1])
|
||||
* basiert auf modernen Netzwerkbibliotheken wie `h11`, `h2`, `wsproto` ([PyPI][1])
|
||||
|
||||
👉 Beispiel:
|
||||
|
||||
```bash
|
||||
hypercorn app:app
|
||||
```
|
||||
|
||||
→ startet deine Web-App und macht sie erreichbar
|
||||
|
||||
---
|
||||
|
||||
# 🔄 2. Abgrenzung zu ähnlichen Begriffen
|
||||
|
||||
## (a) Hypercorn vs. ASGI
|
||||
|
||||
* **ASGI** = Standard / Schnittstelle
|
||||
* **Hypercorn** = konkrete Implementierung dieses Standards
|
||||
|
||||
👉 Vergleich:
|
||||
|
||||
* ASGI ist wie ein „Steckdosenstandard“
|
||||
* Hypercorn ist ein konkretes „Netzteil“
|
||||
|
||||
---
|
||||
|
||||
## (b) Hypercorn vs. andere Server
|
||||
|
||||
### 1. Uvicorn
|
||||
|
||||
* ebenfalls ASGI-Server
|
||||
* sehr schnell, minimalistisch
|
||||
|
||||
👉 Unterschied:
|
||||
|
||||
* Hypercorn: mehr Features (HTTP/2, HTTP/3, mehrere Event-Loops)
|
||||
* Uvicorn: oft performanter, einfacher
|
||||
|
||||
---
|
||||
|
||||
### 2. Gunicorn
|
||||
|
||||
* klassischer WSGI-Server (synchron)
|
||||
|
||||
👉 Unterschied:
|
||||
|
||||
* Gunicorn → synchron (klassisches Request/Response)
|
||||
* Hypercorn → async + moderne Protokolle
|
||||
|
||||
---
|
||||
|
||||
### 3. Daphne
|
||||
|
||||
* ASGI-Server speziell für Django Channels
|
||||
|
||||
👉 Unterschied:
|
||||
|
||||
* Hypercorn → universeller
|
||||
* Daphne → stärker Django-zentriert
|
||||
|
||||
---
|
||||
|
||||
## (c) ASGI vs. WSGI (wichtige Grundlage)
|
||||
|
||||
| Merkmal | WSGI | ASGI |
|
||||
| ---------- | -------- | --------- |
|
||||
| Modell | synchron | asynchron |
|
||||
| WebSockets | ❌ | ✅ |
|
||||
| Echtzeit | ❌ | ✅ |
|
||||
| Skalierung | begrenzt | besser |
|
||||
|
||||
👉 ASGI erlaubt **gleichzeitige Verarbeitung vieler Verbindungen** (z. B. Chats) ([DEV Community][2])
|
||||
|
||||
---
|
||||
|
||||
# 🚧 3. Welche Probleme löst Hypercorn?
|
||||
|
||||
## Problem 1: Asynchrone Webanwendungen
|
||||
|
||||
Früher:
|
||||
|
||||
* jede Anfrage blockiert einen Thread
|
||||
|
||||
Heute (mit Hypercorn + ASGI):
|
||||
|
||||
* tausende Verbindungen gleichzeitig möglich
|
||||
|
||||
👉 Beispiel:
|
||||
|
||||
* Chat-App mit WebSockets
|
||||
* Live-Dashboard (z. B. Börsenkurse)
|
||||
|
||||
---
|
||||
|
||||
## Problem 2: Moderne Protokolle
|
||||
|
||||
Hypercorn unterstützt:
|
||||
|
||||
* HTTP/2 → Multiplexing
|
||||
* WebSockets → Echtzeitkommunikation
|
||||
* HTTP/3 (optional) → moderne Performance ([PyPI][1])
|
||||
|
||||
👉 Beispiel:
|
||||
|
||||
* Echtzeit-Kollaboration (Google Docs-ähnlich)
|
||||
|
||||
---
|
||||
|
||||
## Problem 3: Flexibilität im Event Loop
|
||||
|
||||
Hypercorn kann verschiedene Laufzeitmodelle nutzen:
|
||||
|
||||
* `asyncio`
|
||||
* `uvloop` (schneller)
|
||||
* `trio` (strukturierte Concurrency)
|
||||
|
||||
👉 Vorteil:
|
||||
→ Entwickler können Architektur anpassen
|
||||
|
||||
---
|
||||
|
||||
## Problem 4: Vereinheitlichung
|
||||
|
||||
Hypercorn kann:
|
||||
|
||||
* **ASGI und WSGI Apps** bedienen ([PyPI][1])
|
||||
|
||||
👉 Beispiel:
|
||||
|
||||
* Alte Django-App (WSGI)
|
||||
* Neue FastAPI-App (ASGI)
|
||||
|
||||
→ beide mit einem Server betreiben
|
||||
|
||||
---
|
||||
|
||||
# ⚠️ 4. Herausforderungen & Nachteile
|
||||
|
||||
## (a) Komplexität von Async
|
||||
|
||||
* Async-Code ist schwieriger zu verstehen
|
||||
* Fehler wie:
|
||||
|
||||
* Race Conditions
|
||||
* Deadlocks
|
||||
|
||||
👉 Beispiel:
|
||||
|
||||
```python
|
||||
await db_call() # blockiert Event Loop wenn falsch implementiert
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (b) Deployment-Komplexität
|
||||
|
||||
Produktionssetup oft nötig:
|
||||
|
||||
* Reverse Proxy (z. B. Nginx)
|
||||
* TLS/HTTPS
|
||||
* Worker-Management ([Linux Command Library][3])
|
||||
|
||||
👉 Beispiel-Setup:
|
||||
|
||||
```
|
||||
Client → Nginx → Hypercorn → FastAPI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (c) Performance-Tuning
|
||||
|
||||
Viele Optionen:
|
||||
|
||||
* Worker-Anzahl
|
||||
* Event Loop
|
||||
* Timeout-Settings
|
||||
|
||||
👉 falsche Konfiguration = schlechte Performance
|
||||
|
||||
---
|
||||
|
||||
## (d) Konkurrenz & Tooling
|
||||
|
||||
* Uvicorn oft „Standard“ bei FastAPI
|
||||
* Community teilweise kleiner
|
||||
|
||||
---
|
||||
|
||||
# 🧪 5. Praxisnahe Beispiele
|
||||
|
||||
## Beispiel 1: FastAPI Backend
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
hypercorn main:app --workers 4
|
||||
```
|
||||
|
||||
👉 Nutzen:
|
||||
|
||||
* parallele Requests
|
||||
* skalierbar
|
||||
|
||||
---
|
||||
|
||||
## Beispiel 2: WebSocket-Server
|
||||
|
||||
```python
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(ws):
|
||||
await ws.accept()
|
||||
while True:
|
||||
data = await ws.receive_text()
|
||||
await ws.send_text(f"Echo: {data}")
|
||||
```
|
||||
|
||||
👉 Ohne ASGI/Hypercorn:
|
||||
→ schwer oder unmöglich
|
||||
|
||||
---
|
||||
|
||||
## Beispiel 3: HTTP/2 API
|
||||
|
||||
```bash
|
||||
hypercorn app:app --certfile cert.pem --keyfile key.pem
|
||||
```
|
||||
|
||||
👉 Vorteil:
|
||||
|
||||
* mehrere Requests über eine Verbindung
|
||||
* bessere Performance bei vielen Ressourcen
|
||||
|
||||
---
|
||||
|
||||
## Beispiel 4: Programmatische Nutzung
|
||||
|
||||
```python
|
||||
from hypercorn.asyncio import serve
|
||||
from hypercorn.config import Config
|
||||
import asyncio
|
||||
|
||||
asyncio.run(serve(app, Config()))
|
||||
```
|
||||
|
||||
👉 Einsatz:
|
||||
|
||||
* Integration in eigene Infrastruktur
|
||||
* Tests
|
||||
|
||||
---
|
||||
|
||||
# 🧩 6. Einordnung im Gesamt-Stack
|
||||
|
||||
Typischer moderner Python-Web-Stack:
|
||||
|
||||
```
|
||||
[Browser]
|
||||
↓
|
||||
[Nginx / Load Balancer]
|
||||
↓
|
||||
[Hypercorn (ASGI Server)]
|
||||
↓
|
||||
[FastAPI / Starlette / Quart]
|
||||
↓
|
||||
[Business Logic / DB]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 🧾 Fazit
|
||||
|
||||
Hypercorn ist:
|
||||
|
||||
✔ ein **moderner, flexibler ASGI-Webserver**
|
||||
✔ besonders geeignet für:
|
||||
|
||||
* Echtzeit-Apps
|
||||
* skalierbare APIs
|
||||
* moderne HTTP-Protokolle
|
||||
|
||||
👉 Seine Stärke liegt in:
|
||||
|
||||
* **Asynchronität**
|
||||
* **Protokollvielfalt**
|
||||
* **Flexibilität im Runtime-Modell**
|
||||
|
||||
👉 Seine Schwächen:
|
||||
|
||||
* höhere Komplexität
|
||||
* stärkere Konkurrenz (z. B. Uvicorn)
|
||||
|
||||
---
|
||||
|
||||
[1]: https://pypi.org/pypi/Hypercorn/?utm_source=chatgpt.com "Hypercorn · PyPI"
|
||||
[2]: https://dev.to/devopsfundamentals/python-fundamentals-asgi-1m67?utm_source=chatgpt.com "Python Fundamentals: asgi - DEV Community"
|
||||
[3]: https://linuxcommandlibrary.com/man/hypercorn?utm_source=chatgpt.com "hypercorn man | Linux Command Library"
|
||||
Reference in New Issue
Block a user