Lab 08: Einfacher Server¶
Dieses Projekt zeigt, wie ein minimalistischer Python-HTTP-Dienst als Wheel paketiert und in einem Docker-Image verwendet wird.
Projektstruktur¶
Die folgenden Dateien bilden das Projekt. Erstelle die Verzeichnisstruktur wie folgt:
08-docker-python-challenge/
├── pyproject.toml
├── .dockerignore
└── src/
└── simple_server/
├── __init__.py
└── server.py
pyproject.toml:
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "simple-server-app"
version = "0.1.0"
description = "Simple HTTP server packaged as a wheel"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "GFU Challenges"}
]
requires-python = ">=3.10"
dependencies = []
[project.scripts]
simple-server = "simple_server.server:main"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
.dockerignore:
.venv
__pycache__
.pytest_cache
.mypy_cache
dist
build
.git
.gitignore
.DS_Store
src/simple_server/__init__.py:
"""Simple HTTP server package."""
__all__ = ["server"]
src/simple_server/server.py:
"""Minimal HTTP server used inside the Docker image."""
from __future__ import annotations
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Tuple
class RootHandler(BaseHTTPRequestHandler):
"""Serve a JSON greeting on the root path."""
server_version = "SimpleServer/0.1"
def do_GET(self) -> None: # noqa: N802 (method name required by BaseHTTPRequestHandler)
self._send_json({"message": "Hello from the simple server!"})
def log_message(self, format: str, *args: object) -> None: # noqa: A003
"""Route logs through stdout instead of stderr."""
print(f"{self.address_string()} - - [{self.log_date_time_string()}] {format % args}")
def _send_json(self, payload: dict) -> None:
body = json.dumps(payload).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def run_server(host: str = "0.0.0.0", port: int | None = None) -> Tuple[str, int]:
"""Run the HTTP server and return the address it is bound to."""
port = port or int(os.getenv("PORT", "8080"))
with ThreadingHTTPServer((host, port), RootHandler) as httpd:
print(f"Started server")
httpd.serve_forever()
return host, port
def main() -> None:
"""Entry-point used by the console script."""
run_server()
if __name__ == "__main__":
main()
Dockerfile für diesen Service erstellen¶
Multi-Stage-Builds trennen den Build-Prozess von der Laufzeitumgebung: Zuerst werden Abhängigkeiten und Werkzeuge in einer Builder-Stage genutzt, danach wandern nur die fertigen Artefakte in eine schlanke Runtime-Stage. So bleibt das finale Image klein und sicher.
-
Basis-Image wählen
Nutzepython:3.11-slimfür beide Stages, um das Image kompakt zu halten und trotzdem alle Werkzeuge zum Bau des Wheels und zum Betrieb der Anwendung zur Verfügung zu haben. -
Builder-Stage definieren
FROM python:3.11-slim AS builder
WORKDIR /app
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
Kopiere nun pyproject.toml, README.md und den Ordner src nach /app. Installiere nun im RUN das Werkzeug
build indem du den Befehl pip install --upgrade pip build ausführst. Führe danach
python -m build --wheel --no-isolation aus, damit das Wheel in dist/ entsteht.
- Runtime-Stage beginnen
FROM python:3.11-slim
WORKDIR /app
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PYTHONUNBUFFERED=1
Kopiere dist/ aus der Builder-Stage (erinnere dich an COPY --from=builder, führe pip install /tmp/dist/*.whl
aus. Lösche danach das temporäre Verzeichnis.
- Port freigeben und Startkommando setzen
ErgänzeEXPOSE 8080und setzeCMD ["simple-server"], damit der Container beim Start automatisch den Server ausführt.
Setze diese Schritte in der genannten Reihenfolge zusammen, um das aktuelle Multistage-Dockerfile nachzubauen.