You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker部署多版本应用仅单版本可调用的问题排查求助

Docker多版本应用部署排查建议

问题背景

我尝试在Docker中部署多版本应用供用户按需选择,目前本地运行着两个容器:programs-v1.0.0(映射端口8000)和programs-v1.2.0(映射端口8080)。其中8000端口的版本通过Python脚本调用正常,但8080端口的版本调用时触发ConnectionResetError错误。两个版本在交互模式下运行均无异常,已修改Python脚本中的IP和端口配置,但问题仍未解决。


Docker容器状态

(base) ➜ apps-dsu git:(main) ✗ docker ps -a
CONTAINER ID  IMAGE       COMMAND         CREATED     STATUS           PORTS          NAMES
8bb4b104a541  programs-v1.2.0  "python3 server/main…"  35 minutes ago  Up 35 minutes        0.0.0.0:8080->8080/tcp  determined_kirch
df184b8387dd  programs-v1.0.0  "python3 server/main…"  41 minutes ago  Up 41 minutes        0.0.0.0:8000->8000/tcp  focused_diffie
6723d92116b2  b2eac8b8ed49   "/bin/sh"        2 months ago   Exited (130) 2 months ago              focused_galileo
278be857e7bb  b9810ceb74d9   "/bin/bash"       2 months ago   Exited (0) 2 months ago               lucid_carver
9a300254de3c  b9810ceb74d9   "/bin/bash"       2 months ago   Exited (129) 2 months ago              exciting_nightingale
27cf6d59ddc3  fb590c4e0825   "/bin/bash"       2 months ago   Exited (0) 2 months ago               busy_rubin
6a00ab114365  c2f43dad5e50   "/bin/bash"       2 months ago   Exited (0) 2 months ago               strange_mclaren
789eef79d1df  4eea455efdac   "/bin/bash"       2 months ago   Exited (127) 2 months ago              fervent_raman

调用错误信息

(base) ➜  al-ex-gaines-thomas-programs-default python ../../../server/script-programs-v2-local-port-8080.py ex11-al -local_storage ./result-v1.2.0.zip
Traceback (most recent call last):
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/site-packages/websockets/legacy/client.py", line 139, in read_http_response
    status_code, reason, headers = await read_response(self.reader)
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/site-packages/websockets/legacy/http.py", line 120, in read_response
    status_line = await read_line(stream)
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/site-packages/websockets/legacy/http.py", line 194, in read_line
    line = await stream.readline()
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/asyncio/streams.py", line 540, in readline
    line = await self.readuntil(sep)
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/asyncio/streams.py", line 632, in readuntil
    await self._wait_for_data('readuntil')
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/asyncio/streams.py", line 517, in _wait_for_data
    await self._waiter
  File "/Users/danielsue/Soft/Anaconda3/lib/python3.8/asyncio/selector_events.py", line 848, in _read_ready__data_received
    data = self._sock.recv(self.max_size)
ConnectionResetError: [Errno 54] Connection reset by peer

镜像构建与容器运行命令

docker build -t programs --build-arg="v1.0.0" --tag=programs-v1.0.0 . -f docker/Dockerfile 
docker run -d -p 8000:8000 programs-v1.0.0

docker build -t programs --build-arg="v1.2.0" --tag=programs-v1.2.0 . -f docker/Dockerfile
docker run -d -p 8080:8080 programs-v1.2.0

服务调用命令

python ../../../server/script-porgrams-v1.0.0-local-port-8000.py ex11-al  #(运行正常)
python ../../../server/script-porgrams-v1.0.0-local-port-8080.py ex11-al  #(触发错误)

调用脚本示例

script-porgrams-v1.0.0-local-port-8000.py

# Example of script-porgrams-v1.0.0-local-port-8000.py
...
if __name__ == "__main__":
    #Change the port accordingly when different versions are running, e.g., from 8000 to 8001.
    #BASE_URL = "34.132.141.185:8000/"
    BASE_URL = "127.0.0.1:8000/"
    RUN_URL = f"http://{BASE_URL}run/"

    # print('argument list', sys.argv)
    PREFIX = sys.argv[1]
    STORAGE_TYPE = sys.argv[2]
    OUTPUT_FILE_PATH = sys.argv[3]
    ARGS = sys.argv[4:]

    # Directory path can also be part of the argument, if local path is provided.
    DIRECTORY_PATH = "./"

    # Generate a random client_id
    client_id = str(uuid.uuid4())
    WS_URL = f"ws://{BASE_URL}ws/{client_id}"

    async def main():
        start_time = time.time()  # Start timing the process

        async with websockets.connect(WS_URL, ping_timeout=60) as websocket:
            print("WebSocket connection established.")

            # Create a task for receiving WebSocket messages
            ws_task = asyncio.create_task(receive_app_output(websocket))

            # Run the app process
            await run_app(
                RUN_URL,
                PREFIX,
                ARGS,
                DIRECTORY_PATH,
                OUTPUT_FILE_PATH,
                STORAGE_TYPE,
                client_id,
            )

            # Wait for the WebSocket task to complete
            await ws_task

        end_time = time.time()  # End timing the process
        print(f"Total process time: {end_time - start_time} seconds")

    asyncio.run(main())

script-porgrams-v1.0.0-local-port-8080.py

# Example of script-porgrams-v1.0.0-local-port-8080.py
...
if __name__ == "__main__":
    #Change the port accordingly when different versions are running, e.g., from 8000 to 8001.
    #BASE_URL = "34.132.141.185:8000/"
    BASE_URL = "127.0.0.1:8080/"
    RUN_URL = f"http://{BASE_URL}run/"

    # print('argument list', sys.argv)
    PREFIX = sys.argv[1]
    STORAGE_TYPE = sys.argv[2]
    OUTPUT_FILE_PATH = sys.argv[3]
    ARGS = sys.argv[4:]

    # Directory path can also be part of the argument, if local path is provided.
    DIRECTORY_PATH = "./"

    # Generate a random client_id
    client_id = str(uuid.uuid4())
    WS_URL = f"ws://{BASE_URL}ws/{client_id}"

    async def main():
        start_time = time.time()  # Start timing the process

        async with websockets.connect(WS_URL, ping_timeout=60) as websocket:
            print("WebSocket connection established.")

            # Create a task for receiving WebSocket messages
            ws_task = asyncio.create_task(receive_app_output(websocket))

            # Run the app process
            await run_app(
                RUN_URL,
                PREFIX,
                ARGS,
                DIRECTORY_PATH,
                OUTPUT_FILE_PATH,
                STORAGE_TYPE,
                client_id,
            )

            # Wait for the WebSocket task to complete
            await ws_task

        end_time = time.time()  # End timing the process
        print(f"Total process time: {end_time - start_time} seconds")

    asyncio.run(main())

服务端main.py示例(本地调试)

@app.post("/run/")
async def run_app(
    prefix: str,
    args: str = "",
    input_zip: UploadFile = File(...),
    client_id: str = "",
    output_file_path: str = "./output_files.zip",
    storage_type: str = "-local_storage",
):
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    parent_folder, output_zip_name = os.path.split(output_file_path)
    if storage_type == "-server_storage":
        print(f"Parent folder: {parent_folder}")
        folder_id = get_drive_folder_id(parent_folder)
        if folder_id is None:
            raise HTTPException(
                status_code=404, detail="Folder path does not exist in Google Drive"
            )
    output_zip_name = f"{os.path.splitext(output_zip_name)[0]}_{timestamp}.zip"
    input_zip_path = os.path.join(os.getcwd(), input_zip.filename)
    with open(input_zip_path, "wb+") as file_object:
        file_object.write(input_zip.file.read())
    with zipfile.ZipFile(input_zip_path, "r") as zip_ref:
        zip_ref.extractall(os.getcwd())

    # Find input files with the given prefix in the current directory
    input_files = [f for f in os.listdir(os.getcwd()) if f.startswith(prefix)]
    if not input_files:
        raise HTTPException(
            status_code=404, detail="No input files found with the given prefix"
        )

    if client_id not in active_websockets:
        raise HTTPException(status_code=404, detail="WebSocket connection not found")

    websocket = active_websockets[client_id]

    app_path = "/usr/programs/src/project/makefile/programs-seq"
    # Construct the command
    cmd = [
        app_path,
        prefix,
    ] + args.split()

    process = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )

    async for line in process.stdout:
        if line:
            await websocket.send_text(line.decode().strip())

    # Wait for the subprocess to finish
    await process.wait()

    # Capture the state of the directory after running app
    after_files = os.listdir(os.getcwd())

    # Determine which files were created by app
    new_files = [
        f
        for f in after_files
        if f.startswith((prefix + "_", "restart")) or f.startswith((prefix + "."))
    ]
    zip_path = os.path.join(os.getcwd(), output_zip_name)
    with zipfile.ZipFile(zip_path, "w") as zipf:
        for file_name in new_files:
            file_path = os.path.join(os.getcwd(), file_name)
            zipf.write(file_path, arcname=file_name)

    # Delete the files after a delay
    all_files_to_delete = (
        new_files
        + [zip_path, input_zip_path]
        # unzipped input files
        + [f for f in os.listdir(os.getcwd()) if f.startswith(prefix)]
    )
    asyncio.create_task(delete_files_later(all_files_to_delete))

    await websocket.send_text("END")

    if storage_type == "-local_storage":
        # Save locally
        return FileResponse(
            path=zip_path, media_type="application/zip", filename="output_files.zip"
        )

    elif storage_type == "-server_storage":
        # Upload to Google Drive and return the link
        gdrive_url = upload_to_google_drive(folder_id, zip_path)
        return {"url": gdrive_url}

排查建议

  • 验证容器内服务监听地址:进入programs-v1.2.0容器,执行netstat -tulpn | grep 8080或ss -tulpn | grep 8080,确认服务监听在0.0.0.0:8080而非127.0.0.1:8080,若仅监听本地回环地址,需修改服务端绑定地址为0.0.0.0。
  • 检查容器内服务启动状态:执行docker logs determined_kirch查看容器日志,排查是否有依赖缺失、端口占用等启动报错。
  • 测试基础网络连通性:本地执行curl http://127.0.0.1:8080/run/ -I测试端口可达性;进入容器内部执行curl http://127.0.0.1:8080/run/ -I,确认服务本身可正常响应。
  • 核对WebSocket路径配置:确认programs-v1.2.0服务端的WebSocket路由/ws/{client_id}配置与v1.0.0一致,检查WebSocket注册逻辑是否存在版本差异。
  • 检查构建过程差异:对比两个版本的Docker构建命令,确认--build-arg参数正确传入,排查Dockerfile中是否存在版本相关的配置差异导致服务启动参数或依赖不同。
  • 排查端口冲突与防火墙:执行lsof -i :8080确认本地8080端口未被其他进程占用;检查本地防火墙是否拦截了8080端口的流量。
  • 单独测试WebSocket连接:使用极简脚本测试8080端口的WebSocket连接,定位问题是否出在WebSocket层面:
    import asyncio
    import websockets
    
    async def test_ws():
        try:
            async with websockets.connect("ws://127.0.0.1:8080/ws/test-id") as websocket:
                print("连接成功")
        except Exception as e:
            print(f"连接失败: {e}")
    
    asyncio.run(test_ws())
    

内容的提问来源于stack exchange,提问作者Daniel Su

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 02:52:02