commit 30de0d61ef146d01eb48208ae15beac59dcf905f Author: Sun Ruoxi Date: Thu Aug 6 11:05:00 2026 +0800 feat: add container restart storage test diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..35cdaf3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c9f16ab --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.10-slim + +WORKDIR /app +COPY test_restart.py /app/test_restart.py + +ENV PORT=8080 +ENV RUN_SECONDS=60 + +EXPOSE 8080 + +HEALTHCHECK --interval=10s --timeout=3s --start-period=3s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=2)" || exit 1 + +CMD ["python", "/app/test_restart.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..2406831 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Container restart file test + +用于测试当前平台在程序正常执行完成、退出码为 `0`、随后自动重启容器时,容器内 `/app` 文件是否保留。 + +镜像启动时会检查并写入 `/app/restart-marker.txt`,在 8080 端口提供 `/health`,默认运行 60 秒后以退出码 0 正常退出。 + +构建并推送: + +```bash +docker build -t /k8s-restart-storage-test: . +docker push /k8s-restart-storage-test: +``` + +平台每次启动时查看日志。如果每次都是: + +```text +marker_existed_before_start=false +``` + +表示上次写入的文件已经丢失。如果第二次及之后显示 `true`,表示平台的这种重启方式保留了容器内文件。 + +日志中的 `pod_name` 相同通常表示同一个 Pod 内重启,名称改变表示创建了新 Pod。平台如果注入了 `POD_UID` 环境变量,日志也会一并打印。 + +可通过环境变量 `RUN_SECONDS` 调整正常退出前的运行秒数。 diff --git a/test_restart.py b/test_restart.py new file mode 100644 index 0000000..00fa319 --- /dev/null +++ b/test_restart.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""提供健康检查,并验证平台重启容器后 /app 中的文件是否保留。""" + +import json +import os +import socket +import sys +import threading +import time +from datetime import datetime, timezone +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path + + +PORT = int(os.getenv("PORT", "8080")) +RUN_SECONDS = int(os.getenv("RUN_SECONDS", "60")) +MARKER = Path(os.getenv("MARKER_FILE", "/app/restart-marker.txt")) + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + if self.path != "/health": + self.send_error(404) + return + payload = json.dumps({"status": "ok"}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, _format: str, *_args: object) -> None: + return + + +def main() -> int: + now = datetime.now(timezone.utc).astimezone().isoformat() + pod_name = os.getenv("POD_NAME", socket.gethostname()) + pod_uid = os.getenv("POD_UID", "unknown") + existed = MARKER.exists() + previous = MARKER.read_text().strip() if existed else "" + + print("=" * 70, flush=True) + print(f"time={now}", flush=True) + print(f"pod_name={pod_name}", flush=True) + print(f"pod_uid={pod_uid}", flush=True) + print(f"marker={MARKER}", flush=True) + print(f"marker_existed_before_start={str(existed).lower()}", flush=True) + print(f"marker_previous_content={previous}", flush=True) + + MARKER.parent.mkdir(parents=True, exist_ok=True) + MARKER.write_text(f"pod={pod_name} uid={pod_uid} written_at={now}\n") + print("marker_written=true", flush=True) + + server = ThreadingHTTPServer(("0.0.0.0", PORT), Handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + print(f"health_url=http://0.0.0.0:{PORT}/health", flush=True) + print(f"will_exit_normally_after={RUN_SECONDS}s", flush=True) + time.sleep(RUN_SECONDS) + server.shutdown() + server.server_close() + print("program completed; exiting normally with code 0", flush=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main())