All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
主要改动: - 新增交互式环境配置脚本 (scripts/setup-env.sh) - 新增本地启动快捷脚本 (scripts/run-local.sh) - 新增环境变量模板文件 (.env.example) - 部署模式改版:使用嵌入式配置 + 环境变量覆盖 - 添加对象存储功能支持 - 改进 IoT 卡片导入任务 - 优化 OpenAPI 文档生成 - 删除旧的配置文件,改用嵌入式默认配置
7.3 KiB
7.3 KiB
技术设计:部署自初始化
Context
当前状态
-
目录创建分散
pkg/storage/s3.go在初始化时创建临时目录- 日志目录依赖 Dockerfile 预创建
- 没有统一的初始化入口
-
配置管理复杂
- 4 个外部配置文件:
config.yaml、config.dev.yaml、config.staging.yaml、config.prod.yaml pkg/config/watcher.go实现热重载(开发阶段不需要)- 必须手动拷贝配置文件才能启动
- 4 个外部配置文件:
-
部署流程繁琐
- 需要手动创建目录结构
- 需要手动拷贝配置文件
- Docker Compose 挂载 configs 目录
约束
- 使用 Go 1.16+ 的
go:embed特性 - 保持 Viper 作为配置解析库
- 容器内以非 root 用户 (appuser:1000) 运行
Goals / Non-Goals
Goals:
- 应用启动时自动创建所有必需目录
- 配置嵌入二进制,无需外部配置文件
- 环境变量作为配置覆盖机制
- 部署流程简化到 1 步
Non-Goals:
- 配置热重载(开发阶段移除)
- 多配置文件支持(统一用嵌入默认值 + 环境变量)
- 向后兼容旧的配置方式
Decisions
Decision 1: 目录初始化放在应用层
选择: 在 main.go 启动时调用集中化的目录初始化函数
备选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| Dockerfile RUN mkdir | 镜像固定 | 不支持运行时配置路径 |
| Entrypoint 脚本 | 运行时动态 | Shell 脚本维护成本高 |
| 应用代码初始化 | 跨环境通用、错误处理清晰 | 无 |
理由: 应用代码可以读取配置中的路径,提供 Go 级别的错误处理,并在所有部署环境(Docker、K8s、裸机)通用。
Decision 2: 配置嵌入 + 环境变量覆盖
选择: 使用 go:embed 嵌入默认配置,环境变量覆盖
配置优先级:
环境变量 (JUNHONG_*) > 嵌入默认值
备选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 纯外部文件 | 运行时可改 | 部署复杂 |
| 纯环境变量 | 12-Factor | 复杂配置难表达 |
| 嵌入 + 环境变量 | 开箱即用 + 灵活覆盖 | 改默认值需重编译 |
理由: 嵌入配置保证"开箱即用",环境变量覆盖满足生产环境定制需求。开发阶段不需要频繁改默认值。
Decision 3: 环境变量命名规范
选择: JUNHONG_ 前缀 + 下划线分隔
格式: JUNHONG_{SECTION}_{KEY}
示例:
JUNHONG_DATABASE_HOST=localhost
JUNHONG_DATABASE_PORT=5432
JUNHONG_REDIS_ADDRESS=localhost:6379
JUNHONG_SERVER_ADDRESS=:3000
理由:
- 前缀避免与系统环境变量冲突
- 下划线分隔便于 Viper 的
SetEnvKeyReplacer处理 - 符合业界惯例(类似
POSTGRES_、REDIS_等)
Decision 4: 删除配置热重载
选择: 删除 pkg/config/watcher.go 及相关逻辑
理由:
- 开发阶段,配置变更频率低
- 重启容器即可应用新配置
- 减少代码复杂度
Decision 5: 目录降级策略
选择: 权限不足时使用系统临时目录作为 fallback
if err := os.MkdirAll(dir, 0755); err != nil {
if os.IsPermission(err) {
fallback := filepath.Join(os.TempDir(), "junhong", filepath.Base(dir))
os.MkdirAll(fallback, 0755)
return fallback, nil
}
return "", err
}
理由: 提高容错性,即使权限配置不当也能启动(降级运行)。
Architecture
启动流程
main.go
│
├── 1. config.Load() // 加载嵌入配置 + 环境变量覆盖
│ ├── 读取 go:embed 的 defaults/config.yaml
│ ├── 应用 JUNHONG_* 环境变量覆盖
│ └── 验证必填配置
│
├── 2. bootstrap.EnsureDirectories(cfg) // 创建所有必需目录
│ ├── 临时文件目录
│ ├── 日志目录
│ └── 其他运行时目录
│
├── 3. logger.Init(cfg) // 初始化日志
│
├── 4. database.Init(cfg) // 初始化数据库
│
└── 5. ... 其他组件初始化
目录结构
pkg/
├── bootstrap/
│ └── directories.go # 新增:目录初始化
├── config/
│ ├── config.go # 保留:配置结构定义
│ ├── loader.go # 重写:嵌入配置加载
│ ├── embedded.go # 新增:go:embed 逻辑
│ ├── defaults/
│ │ └── config.yaml # 新增:嵌入的默认配置
│ └── watcher.go # 删除
嵌入配置内容
# pkg/config/defaults/config.yaml
server:
address: ":3000"
read_timeout: 30s
write_timeout: 30s
shutdown_timeout: 30s
prefork: false
database:
host: "" # 必须通过 JUNHONG_DATABASE_HOST 设置
port: 5432
user: "" # 必须通过 JUNHONG_DATABASE_USER 设置
password: "" # 必须通过 JUNHONG_DATABASE_PASSWORD 设置
dbname: "" # 必须通过 JUNHONG_DATABASE_DBNAME 设置
sslmode: "disable"
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 5m
redis:
address: "" # 必须通过 JUNHONG_REDIS_ADDRESS 设置
port: 6379
password: ""
db: 0
pool_size: 10
min_idle_conns: 5
dial_timeout: 5s
read_timeout: 3s
write_timeout: 3s
storage:
provider: "s3"
temp_dir: "/tmp/junhong-storage"
s3:
endpoint: ""
region: ""
bucket: ""
access_key_id: ""
secret_access_key: ""
use_ssl: false
path_style: true
presign:
upload_expires: 15m
download_expires: 24h
logging:
level: "info"
development: false
app_log:
filename: "/app/logs/app.log"
max_size: 100
max_backups: 3
max_age: 7
compress: true
access_log:
filename: "/app/logs/access.log"
max_size: 100
max_backups: 3
max_age: 7
compress: true
queue:
concurrency: 10
retry_max: 5
timeout: 10m
jwt:
secret_key: "" # 必须通过 JUNHONG_JWT_SECRET_KEY 设置
token_duration: 24h
access_token_ttl: 24h
refresh_token_ttl: 168h
middleware:
enable_rate_limiter: false
rate_limiter:
max: 100
expiration: 1m
storage: "memory"
Risks / Trade-offs
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| 嵌入配置修改需重新编译 | 低(开发阶段) | 敏感配置通过环境变量,嵌入的只是默认值 |
| 环境变量泄露 | 中 | 使用 K8s Secrets 或 Docker Secrets 管理 |
| 目录降级后行为不一致 | 低 | 降级时打印 WARN 日志,明确告知 |
| 删除热重载后调试不便 | 低 | 开发时直接重启进程,生产用 rolling update |
Migration Plan
实施步骤
-
新增目录初始化模块
- 创建
pkg/bootstrap/directories.go - 在 main.go 中调用
- 创建
-
新增配置嵌入模块
- 创建
pkg/config/defaults/config.yaml - 创建
pkg/config/embedded.go - 重写
pkg/config/loader.go
- 创建
-
清理旧配置逻辑
- 删除
pkg/config/watcher.go - 删除
configs/*.yaml
- 删除
-
更新 Docker 配置
- 更新
Dockerfile.api和Dockerfile.worker - 重写
docker-compose.prod.yml
- 更新
-
更新文档
- 更新 README.md 部署说明
- 更新环境变量文档
回滚策略
如需回滚,恢复 git 提交即可(开发阶段无生产数据风险)。
Open Questions
无。设计已明确,可直接实施。