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
相关产品推荐
相关产品推荐

