Zum Inhalt

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.

  1. Basis-Image wählen
    Nutze python:3.11-slim fü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.

  2. 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.

  1. 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.

  1. Port freigeben und Startkommando setzen
    Ergänze EXPOSE 8080 und setze CMD ["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.