部署时报错#1 [sensor-server internal] load build definition from Dockerfile #1 transferring dockerfile: 517B done #1 DONE 0.0s #2 [sensor-server internal] load metadata for docker.io/library/python:3.9-slim-bullseye #2 DONE 1.5s failed to solve: invalid containerPort: # Exited:0
时间: 2025-07-30 22:35:44 浏览: 3
<think>根据错误信息,问题出现在docker-compose.yml文件中,具体错误是“invalidcontainerPort:#”。这通常是因为在ports映射配置中出现了注释符号“#”导致语法错误。在docker-compose.yml中,注释应该单独一行,不能与配置项在同一行。检查docker-compose.yml文件,在ports部分,可能出现了类似这样的行:-"8765:8765"#WebSocket在docker-compose.yml中,行内注释是不允许的。因此,需要将注释移除或者放到单独一行。修改建议:1.移除端口映射行内的注释2.或者将注释放在端口映射行的上方修改前:ports:-"8765:8765"#WebSocket-"8080:8080"#HTTPAPI修改后:ports:#WebSocket-"8765:8765"#HTTPAPI-"8080:8080"或者直接移除注释:ports:-"8765:8765"-"8080:8080"另外,错误信息中还有“Exited:0”,表示容器启动后立即退出,这通常是因为容器内的主进程退出。我们需要确保主进程(即server.py)能够持续运行。在之前的代码中,我们使用了asyncio事件循环来运行主程序。但是,在Docker环境中,如果主程序没有正确设置为前台运行,容器可能会退出。我们已经使用了无限循环,所以应该可以持续运行。但是,为了确保Docker容器能够持续运行,我们需要确认以下几点:1.主程序没有因为异常而退出2.日志输出到控制台(stdout)以便Docker捕获在日志配置中,我们同时使用了控制台和文件日志。在Docker中,控制台日志会被捕获,所以我们需要确保日志也输出到控制台。在server.py的日志配置中,我们已经添加了控制台处理器(console_handler),所以日志会输出到控制台。另外,我们还需要确保数据库连接等初始化过程成功,否则主程序可能会因为异常退出。我们在数据库连接和初始化中已经加入了重试机制,应该能够处理短暂的数据库连接问题。如果问题仍然存在,我们可以通过以下命令查看容器日志来诊断:dockerlogssensor-server下面给出修正后的docker-compose.yml文件(去掉行内注释):</think>根据您提供的错误信息,问题出在 Dockerfile 的 `EXPOSE` 指令上。错误信息 `invalid containerPort: #` 表明 Docker 在解析端口声明时遇到了问题。让我们解决这个问题并提供完整的解决方案。
## 问题分析与解决方案
### 错误原因
在 Dockerfile 中,`EXPOSE` 指令后不能有注释。您可能在端口声明后添加了注释导致语法错误:
```dockerfile
EXPOSE 8765 # WebSocket端口 <-- 这是无效的
EXPOSE 8080 # HTTP API端口 <-- 这是无效的
```
### 修复后的 Dockerfile
```dockerfile
# 使用Python 3.9官方镜像
FROM python:3.9-slim-bullseye
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
build-essential \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制项目文件
COPY . /app
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 设置环境变量
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH /app
# 暴露端口(移除注释)
EXPOSE 8765
EXPOSE 8080
# 启动命令(使用asyncio运行)
CMD ["python", "-u", "server.py"]
```
### 完整的 docker-compose.yml
```yaml
version: '3.8'
services:
sensor-server:
build:
context: .
dockerfile: Dockerfile
container_name: sensor-server
ports:
- "8765:8765" # WebSocket
- "8080:8080" # HTTP API
environment:
- TZ=Asia/Shanghai
volumes:
- ./logs:/app/logs
- ./config.json:/app/config.json
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/devices || exit 1"]
interval: 30s
timeout: 10s
retries: 3
mysql:
image: mysql:8.0
container_name: sensor-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
volumes:
mysql_data:
```
### 完整的 server.py (修复版本)
```python
import asyncio
import websockets
import json
import struct
import datetime
import logging
import traceback
import mysql.connector
from mysql.connector import pooling, errorcode
from typing import Dict, List, Optional
import aiohttp
from aiohttp import web
import time
import os
import jwt
import numpy as np
from dateutil import parser
# ======================
# 配置参数(增强环境变量支持)
# ======================
def load_config():
"""从config.json加载配置,支持环境变量覆盖"""
default_config = {
"AUTH_ENABLED": False,
"API_AUTH_ENABLED": True,
"API_SECRET_KEY": "your-secret-key-here",
"API_TOKEN_EXPIRY": 3600,
"WEBSOCKET_HOST": "0.0.0.0",
"WEBSOCKET_PORT": 8765,
"HTTP_PORT": 8080,
"MAX_CONNECTIONS": 100,
"DB_WRITE_BATCH_SIZE": 50,
"DB_WRITE_TIMEOUT": 5.0,
"MYSQL": {
"HOST": "mysql",
"PORT": 3306,
"USER": "root",
"PASSWORD": "password",
"DATABASE": "sensor_db"
},
"API_RATE_LIMITS": {
"latest": 60,
"history": 30,
"subscribe": 10
}
}
try:
# 尝试从环境变量加载配置
config = {}
for key in default_config:
env_value = os.getenv(key.upper())
if env_value is not None:
try:
# 尝试解析JSON格式的环境变量
config[key] = json.loads(env_value)
except json.JSONDecodeError:
# 如果不是JSON,直接使用字符串值
config[key] = env_value
else:
config[key] = default_config[key]
# 特殊处理MySQL配置
for key in default_config["MYSQL"]:
env_key = f"MYSQL_{key.upper()}"
env_value = os.getenv(env_key)
if env_value is not None:
config["MYSQL"][key] = env_value
return config
except Exception as e:
logging.error(f"加载配置失败,使用默认配置: {str(e)}")
return default_config
CONFIG = load_config()
# ======================
# 日志系统(优化Docker日志)
# ======================
def setup_logging():
"""配置日志系统"""
logger = logging.getLogger("SensorServer")
logger.setLevel(logging.INFO)
# 创建logs目录(用于Docker卷挂载)
log_dir = 'logs'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 控制台处理器(输出到stdout)
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
))
logger.addHandler(console_handler)
# 文件处理器(输出到文件)
file_handler = logging.FileHandler(f'{log_dir}/sensor_server.log')
file_handler.setFormatter(logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
))
logger.addHandler(file_handler)
return logger
# 全局日志器
logger = setup_logging()
# ======================
# MySQL数据库管理(带重试机制)
# ======================
class MySQLDatabaseManager:
def __init__(self):
self.data_buffer = []
self.last_write_time = datetime.datetime.now()
self.connection_pool = self._create_connection_pool()
self._init_db()
self.device_cache = {}
self.last_device_refresh = datetime.datetime.now()
def _create_connection_pool(self) -> pooling.MySQLConnectionPool:
"""创建MySQL连接池(带重试机制)"""
max_retries = 5
retry_delay = 5
for attempt in range(max_retries):
try:
logger.info(f"尝试连接MySQL数据库 (尝试 {attempt+1}/{max_retries})")
return pooling.MySQLConnectionPool(
pool_name="sensor_pool",
pool_size=5,
pool_reset_session=True,
host=CONFIG["MYSQL"]["HOST"],
port=CONFIG["MYSQL"]["PORT"],
user=CONFIG["MYSQL"]["USER"],
password=CONFIG["MYSQL"]["PASSWORD"],
database=CONFIG["MYSQL"]["DATABASE"]
)
except mysql.connector.Error as err:
logger.error(f"MySQL连接失败: {err}")
if attempt < max_retries - 1:
logger.info(f"{retry_delay}秒后重试...")
time.sleep(retry_delay)
else:
logger.critical("MySQL连接重试次数用尽")
raise
except Exception as err:
logger.error(f"创建连接池错误: {err}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
raise
def _init_db(self) -> None:
"""初始化数据库表结构(带重试机制)"""
max_retries = 3
for attempt in range(max_retries):
try:
logger.info(f"初始化数据库 (尝试 {attempt+1}/{max_retries})")
conn = self.connection_pool.get_connection()
cursor = conn.cursor()
# 创建设备表
cursor.execute('''
CREATE TABLE IF NOT EXISTS devices (
id INT AUTO_INCREMENT PRIMARY KEY,
device_id VARCHAR(50) NOT NULL UNIQUE,
device_name VARCHAR(100),
location VARCHAR(100),
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
''')
# 创建传感器数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS sensor_data (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
device_id INT NOT NULL,
timestamp TIMESTAMP NOT NULL,
sensor_values JSON NOT NULL,
received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_device_id (device_id),
INDEX idx_timestamp (timestamp),
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
)
''')
# 创建API访问日志表
cursor.execute('''
CREATE TABLE IF NOT EXISTS api_access_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
endpoint VARCHAR(50) NOT NULL,
client_ip VARCHAR(45) NOT NULL,
device_id VARCHAR(50),
parameters TEXT,
status_code INT NOT NULL,
response_time FLOAT NOT NULL,
access_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_endpoint (endpoint),
INDEX idx_access_time (access_time)
)
''')
conn.commit()
logger.info("数据库表初始化完成")
return
except mysql.connector.Error as err:
logger.error(f"数据库初始化失败: {err}")
if attempt < max_retries - 1:
time.sleep(5)
else:
logger.critical("数据库初始化重试次数用尽")
raise
except Exception as e:
logger.error(f"数据库初始化异常: {e}")
if attempt < max_retries - 1:
time.sleep(5)
else:
raise
finally:
if 'conn' in locals() and conn.is_connected():
conn.close()
# 其他方法保持不变...
# ======================
# 主程序(增强健壮性)
# ======================
async def main():
logger.info("=" * 60)
logger.info("传感器数据采集服务器启动")
logger.info(f"时间: {datetime.datetime.now()}")
logger.info(f"配置详情: {json.dumps(CONFIG, indent=2)}")
logger.info("=" * 60)
try:
# 初始化MySQL数据库管理器
db_manager = MySQLDatabaseManager()
# 创建WebSocket服务器
ws_server = SensorWebSocketServer(
CONFIG["WEBSOCKET_HOST"],
CONFIG["WEBSOCKET_PORT"],
db_manager
)
# 创建API服务器
api_server = APIServer(db_manager, CONFIG["HTTP_PORT"])
# 启动服务器
await ws_server.start()
await api_server.start()
logger.info("所有服务已成功启动,进入主循环")
try:
# 运行主循环
while True:
try:
await asyncio.sleep(5)
# 定期刷新数据库缓冲区
db_manager.flush_buffer()
except Exception as loop_err:
logger.error(f"主循环发生异常: {loop_err}")
except asyncio.CancelledError:
logger.info("收到取消信号")
except KeyboardInterrupt:
logger.info("收到键盘中断信号")
except Exception as main_err:
logger.critical(f"服务器启动失败: {main_err}")
logger.critical(f"异常详情:\n{traceback.format_exc()}")
# 等待一段时间避免立即重启
await asyncio.sleep(10)
raise
finally:
try:
# 确保所有缓冲数据都写入数据库
db_manager.flush_buffer()
await ws_server.stop()
await api_server.stop()
except Exception as e:
logger.error(f"关闭过程中出错: {e}")
# ======================
# Docker启动入口
# ======================
if __name__ == "__main__":
# 配置日志以确保在Docker中可见
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
# 创建事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
# 启动主程序
loop.run_until_complete(main())
except KeyboardInterrupt:
logger.info("程序被用户中断")
except Exception as e:
logger.critical(f"未处理的异常: {e}")
finally:
# 关闭事件循环
if not loop.is_closed():
loop.close()
logger.info("服务器已完全关闭")
```
## 部署步骤说明
### 1. 创建项目结构
```
sensor-server/
├── Dockerfile # 修复后的Dockerfile
├── docker-compose.yml # 修复后的compose文件
├── config.json # 配置文件
├── requirements.txt # 依赖文件
└── server.py # 主程序文件
```
### 2. 创建配置文件 (config.json)
```json
{
"AUTH_ENABLED": false,
"API_AUTH_ENABLED": true,
"WEBSOCKET_PORT": 8765,
"HTTP_PORT": 8080,
"MYSQL": {
"HOST": "mysql",
"PORT": 3306
}
}
```
### 3. 创建依赖文件 (requirements.txt)
```
aiohttp==3.8.4
websockets==11.0.3
mysql-connector-python==8.0.33
python-dateutil==2.8.2
PyJWT==2.6.0
numpy==1.24.3
```
### 4. 创建 .env 文件
```env
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_DATABASE=sensor_db
MYSQL_USER=sensor_user
MYSQL_PASSWORD=sensor_password
```
### 5. 构建并启动容器
```bash
docker-compose up -d --build
```
### 6. 验证部署
```bash
docker logs sensor-server
# 应该看到 "传感器数据采集服务器启动" 消息
docker ps
# 应该看到 sensor-server 和 sensor-mysql 容器运行中
```
## 关键修复点说明
1. **Dockerfile 修复**:
- 移除了 `EXPOSE` 指令后的注释
- 添加了系统依赖安装(build-essential, libssl-dev)
- 使用 `-u` 参数运行 Python 确保无缓冲输出
2. **配置加载增强**:
- 支持环境变量覆盖所有配置项
- 自动处理环境变量中的 JSON 数据
- 提供详细的配置日志输出
3. **MySQL 连接优化**:
- 完善的连接池重试机制
- 数据库初始化错误处理
- 确保连接正确关闭
4. **日志系统改进**:
- 确保日志输出到控制台(Docker 要求)
- 创建日志目录防止启动失败
- 详细的异常日志记录
5. **主程序健壮性**:
- 完整的异常处理链
- 资源清理保证(finally 块)
- 事件循环正确管理
## 常见问题解决方案
### 1. 端口冲突问题
如果端口已被占用,修改 `docker-compose.yml`:
```yaml
ports:
- "18765:8765" # 修改外部端口
- "18080:8080" # 修改外部端口
```
### 2. MySQL 连接问题
检查 `.env` 文件中的凭据是否与 MySQL 容器匹配,确保:
```bash
docker-compose exec mysql mysql -u sensor_user -p
# 输入密码后应能连接
```
### 3. 构建缓存问题
如果构建失败,清除缓存重建:
```bash
docker-compose build --no-cache
docker-compose up -d
```
阅读全文
相关推荐


















