Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录
切换导航
U
ubains-module-test
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
1
议题
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
郑晓兵
ubains-module-test
Commits
1e8018f7
提交
1e8018f7
authored
9月 01, 2026
作者:
陈泽健
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
docs(自动化部署): ujava2 脚本死锁问题处理与 fastdfs 临时目录分析报告
- _PRD_ujava2脚本死锁导致日志停止_问题处理及计划执行 - fastdfs_start 问题分析报告 - fastdfs 重启问题业主说明(两版)
上级
8a91d8c5
隐藏空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
690 行增加
和
0 行删除
+690
-0
_PRD_ujava2脚本死锁导致日志停止_问题处理.md
Docs/PRD/自动化部署脚本/新统一平台/问题处理/_PRD_ujava2脚本死锁导致日志停止_问题处理.md
+174
-0
_PRD_ujava2脚本死锁导致日志停止_问题处理_计划执行.md
...PRD/自动化部署脚本/新统一平台/问题处理/_PRD_ujava2脚本死锁导致日志停止_问题处理_计划执行.md
+145
-0
fastdfs_start_问题分析报告.md
自动化部署脚本/arm架构/临时目录/fastdfs_start_问题分析报告.md
+277
-0
fastdfs重启问题_业主说明_两版.md
自动化部署脚本/arm架构/临时目录/fastdfs重启问题_业主说明_两版.md
+94
-0
没有找到文件。
Docs/PRD/自动化部署脚本/新统一平台/问题处理/_PRD_ujava2脚本死锁导致日志停止_问题处理.md
0 → 100644
浏览文件 @
1e8018f7
# _PRD_ujava2脚本死锁导致日志停止_问题处理
> 脚本来源:
-
`自动化部署脚本/x86架构/新统一平台/定时脚本/ujava2-startup.sh`
-
服务器部署路径:
`/data/services/scripts/ujava2-startup.sh`
(192.168.5.70)
## 1. 背景与目标
### 1.1 背景
服务器 192.168.5.70(新统一平台)上,定时脚本
`ujava2-startup.sh`
通过 crontab 每 3 分钟执行一次(
`*/3 * * * *`
)。
用户发现
`ujava2-service-manager.log`
日志一直不更新,怀疑脚本异常停止。
### 1.2 目标
1.
定位脚本日志停止的真正根因
2.
从源头修复,确保脚本每次 cron 触发都能正常完整执行
3.
产出问题处理文档与计划执行文档,同步仓库脚本与部署脚本
---
## 2. 问题报错信息
### 2.1 现象一:服务日志停止更新
```
# /data/logs/ujava2-service-manager.log 最后修改时间停留在 13:26,之后不再更新
-rw-r--r-- 1 root root 1795 9月 1 13:26 /data/logs/ujava2-service-manager.log
```
### 2.2 现象二:cron 日志每 3 分钟打印"另一个实例正在运行"
```
# /var/log/ujava2-cron.log
2026-09-01 13:26:00 [WARNING] 6060端口未监听,将启动malan服务
2026-09-01 13:26:24 [SUCCESS] malan 启动成功,6060端口已监听
===== 2026-09-01 13:26:24 操作完成 =====
2026-09-01 13:27:02 [WARNING] 另一个实例正在运行,退出 ← 从此刻起约 2 小时全部如此
2026-09-01 13:30:01 [WARNING] 另一个实例正在运行,退出
...(持续到 15:15 诊断时仍在重复)
```
### 2.3 现象三:cron 日志混入 `tee` 报错(部署旧版本的附加缺陷)
```
tee: '': 没有那个文件或目录
2026-09-01 14:18:01 [WARNING] 另一个实例正在运行,退出
```
### 2.4 现象四:flock 锁被 malan 进程持有
```
# fuser -v /tmp/ujava2-startup.lock
/tmp/ujava2-startup.lock:
root 70515 F.... malan ← PID 70515 为 ./malan 守护进程
# malan 进程 13:26:00 被脚本拉起,与锁释放的时间点完全吻合
ps -o pid,lstart,cmd -p 70515
PID STARTED CMD
70515 Tue Sep 1 13:26:00 2026 ./malan
# malan 进程 fd 200 确实持有锁
ls -la /proc/70515/fd | grep 200
l-wx------ 1 root root 64 9月 1 15:16 200 -> /tmp/ujava2-startup.lock
```
---
## 3. 问题解决分析
### 3.1 问题根因
**直接根因:后台子进程继承 flock 文件锁,导致死锁永久占锁。**
1.
脚本开头用
`exec 200>"$LOCK_FILE"`
+
`flock -n 200`
实现并发控制
2.
13:26 脚本检测到 6060 端口未监听,调用
`start_malan_service`
3.
该函数执行
`bash -c "source /etc/profile && ./malan" &`
**后台启动 malan**
4.
后台子进程(
`bash -c`
→
`./malan`
)
**完整继承父脚本的 fd 200 锁**
5.
malan 是长期驻留的守护进程
`./malan`
(PID 70515,从 13:26 一直运行到现在),
**永不退出**
6.
父脚本执行完后自身退出,但 malan 依然持有 fd 200 →
**锁被永久占用**
7.
此后每 3 分钟 cron 触发脚本,
`flock -n`
失败,打印"另一个实例正在运行,退出"后 exit 1
8.
脚本主流程(API 检查、服务检查、重启、6060 监测)
**全部不再执行**
→ 服务日志停止更新
**附加缺陷:部署版脚本 `LOG_FILE` 定义在 flock 之后。**
-
服务器部署版本中
`LOG_FILE="/data/logs/ujava2-service-manager.log"`
在 flock 代码块之后
-
若 flock 分支先执行,
`$LOG_FILE`
为空 →
`tee -a ""`
报
`tee: '': 没有那个文件或目录`
-
本地仓库脚本已修复(LOG_FILE 提到 flock 之前 + mkdir),部署版是旧版本
**版本差异确认:**
-
本地仓库脚本(source of truth)已含全部修复:
`start_malan_service`
/
`restart_host_extapi_service`
后台启动前均有
`exec 200>&-`
关闭锁 fd,
`LOG_FILE`
在 flock 前定义
-
服务器部署的是旧版本脚本,缺失以上修复(diff 确认)
-
其余差异(
`RETRY_INTERVAL`
20 vs 30、
`stop_service`
kill 逻辑)为既有配置差异,与本次问题无关,未改动
### 3.2 涉及文件
| 文件 | 说明 |
|------|------|
| 服务器
`/data/services/scripts/ujava2-startup.sh`
| 部署脚本(旧版,缺三处修复) |
| 服务器
`/data/services/scripts/ujava2-startup.sh.bak.20260901`
| 修复前备份 |
| 本地
`自动化部署脚本/x86架构/新统一平台/定时脚本/ujava2-startup.sh`
| 仓库脚本(已含修复,无需改动) |
| 服务器
`/tmp/ujava2-startup.lock`
| flock 锁文件(被 malan 持有) |
| 服务器
`/data/logs/ujava2-service-manager.log`
| 服务日志 |
| 服务器
`/var/log/ujava2-cron.log`
| cron 输出日志 |
### 3.3 代码验证
**修复前(服务器部署版,缺关闭 fd):**
```
bash
start_malan_service
()
{
...
# 加载环境变量,然后启动malan
bash
-c
"source /etc/profile && ./malan"
&
local
start_pid
=
$!
...
}
```
`./malan`
继承 fd 200 锁 → 永久占锁。
**修复后(与仓库脚本一致):**
```
bash
start_malan_service
()
{
...
# 关闭锁文件描述符,防止子进程继承导致锁无法释放
exec
200>&-
# 加载环境变量,然后启动malan
bash
-c
"source /etc/profile && ./malan"
&
...
}
```
`restart_host_extapi_service`
同样在
`bash -c "... ./run.sh" &`
前加
`exec 200>&-`
。
`LOG_FILE`
定义移至 flock 之前并加
`mkdir -p "$(dirname "$LOG_FILE")"`
。
**验证结果(2026-09-01 15:46-15:54):**
-
手动执行修复后脚本:
`EXIT_CODE=0`
,完整跑通 API 检查、服务自愈、6060 监测
-
malan 被重启为新进程 PID 128794,
**不再持有 /tmp/ujava2-startup.lock**
-
下一轮 cron(15:51、15:54)日志继续正常完整执行,锁在执行完毕后自动释放
-
`fuser /tmp/ujava2-startup.lock`
在 cron 运行间隙无输出(无持锁进程)
### 3.4 解决方案
| 步骤 | 操作 | 状态 |
|------|------|------|
| 1 | 备份服务器脚本
`cp ujava2-startup.sh ujava2-startup.sh.bak.20260901`
| ✅ 完成 |
| 2 | 服务器脚本
`start_malan_service`
后台启动前加
`exec 200>&-`
| ✅ 完成(与仓库版一致) |
| 3 | 服务器脚本
`restart_host_extapi_service`
后台启动前加
`exec 200>&-`
| ✅ 完成(与仓库版一致) |
| 4 |
`LOG_FILE`
定义移至 flock 之前 +
`mkdir -p`
| ✅ 完成(与仓库版一致) |
| 5 |
`bash -n`
语法校验 | ✅ SYNTAX_OK |
| 6 | 释放死锁:kill 持锁 malan(70515),脚本自动拉起新 malan | ✅ 完成 |
| 7 | 手动执行一次验证 + 等待两轮 cron 确认自动恢复 | ✅ 完成 |
### 3.5 预防措施
1.
**凡是脚本内用 `flock` 加锁后再 `&` 后台启动的守护进程,启动前必须先 `exec 200>&-` 关闭锁 fd**
——否则锁被子进程继承后永久占用
2.
新增定时脚本统一模板:
`LOG_FILE`
在 flock 之前定义 +
`mkdir -p`
,避免 flock 分支日志写空路径
3.
服务器脚本事后需与仓库脚本 diff 对齐:本次回填前发现服务器部署版落后于仓库版(缺三处修复),后续部署流程应校验差异
4.
排查"脚本不打印日志"问题时,优先检查两类信号:
-
锁文件持有者:
`fuser -v /tmp/ujava2-startup.lock`
(死锁从 13:27 即被锁,日志已明确提示"另一个实例正在运行",是脚本自身的保护机制在如实工作)
-
cron 日志
`/var/log/ujava2-cron.log`
最后几行,比服务日志更能反映脚本真实状态
### 3.6 问题状态
-
[
x
]
定位根因
-
[
x
]
问题文档输出
-
[
x
]
计划执行文档输出
-
[
x
]
服务器脚本修复并验证(cron 自动恢复)
-
[
x
]
死锁释放
-
[
]
仓库脚本与服务器脚本完全对齐(含
`RETRY_INTERVAL`
/
`stop_service`
差异确认,非本次问题范围)
### 规范文档
-
代码规范:
`Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
-
问题总结:
`Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
-
方法总结:
`Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
-
文档规范:
`Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
-
测试规范:
`Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
-
历史同类问题:
`Docs/PRD/自动化部署脚本/新统一平台/问题处理/java监测脚本执行异常_问题处理.md`
---
*文档结束*
\ No newline at end of file
Docs/PRD/自动化部署脚本/新统一平台/问题处理/_PRD_ujava2脚本死锁导致日志停止_问题处理_计划执行.md
0 → 100644
浏览文件 @
1e8018f7
# ujava2监测脚本flock死锁导致日志停止 - 问题处理计划执行文档
> PRD来源: `Docs/PRD/自动化部署脚本/新统一平台/问题处理/_PRD_ujava2脚本死锁导致日志停止_问题处理.md`
> 脚本: `自动化部署脚本/x86架构/新统一平台/定时脚本/ujava2-startup.sh`
> 服务器: 192.168.5.70 `/data/services/scripts/ujava2-startup.sh`
---
## 1. 问题分析
### 1.1 问题描述
crontab 每 3 分钟执行的
`ujava2-startup.sh`
,服务日志
`/data/logs/ujava2-service-manager.log`
自 13:26 后停止更新。
### 1.2 问题日志片段
```
2026-09-01 13:26:00 [WARNING] 6060端口未监听,将启动malan服务
2026-09-01 13:26:24 [SUCCESS] malan 启动成功,6060端口已监听
===== 2026-09-01 13:26:24 操作完成 =====
2026-09-01 13:27:02 [WARNING] 另一个实例正在运行,退出
tee: '': 没有那个文件或目录
2026-09-01 13:30:01 [WARNING] 另一个实例正在运行,退出
...(持续重复至 15:15 诊断时)
```
### 1.3 根因分析
| 问题点 | 说明 |
|--------|------|
| 死锁根因 |
`start_malan_service`
执行
`bash -c "source /etc/profile && ./malan" &`
后台启动 malan,子进程
**继承 fd 200(flock 锁)**
。malan 为常驻守护进程永不退出 → 锁被永久占用 |
| 触发时机 | 13:26 脚本检测到 6060 端口未监听 → 调用
`start_malan_service`
→ malan(PID 70515)持锁,与用户确认的"13:26 自动化部署完毕"时间完全吻合 |
| 连锁反应 | 此后每 3 分钟 cron 触发
`flock -n 200`
失败 → 打印 WARNING 后 exit 1 → 主流程(API 检查/服务检查/自愈/6060 监测)全部不执行 |
| 附加缺陷 | 部署旧版脚本
`LOG_FILE`
定义在 flock 之后 →
`tee -a "$LOG_FILE"`
写空路径报
`tee: '': 没有那个文件或目录`
|
| 证据 |
`fuser -v /tmp/ujava2-startup.lock`
→ PID 70515 (malan);
`ls -la /proc/70515/fd | grep 200`
→
`200 -> /tmp/ujava2-startup.lock`
;malan 启动时间 13:26:00 |
---
## 2. 修复方案
### 2.1 方案设计(已采纳:修复脚本 + 释放锁)
**原则:从源头修复,与本地仓库脚本(source of truth)对齐,而非仅临时释放锁。**
| 修改点 | 内容 | 目的 |
|--------|------|------|
| 修改点1 |
`start_malan_service`
在
`./malan &`
前加
`exec 200>&-`
| 关闭锁 fd,防止 malan 继承锁 |
| 修改点2 |
`restart_host_extapi_service`
在
`./run.sh &`
前加
`exec 200>&-`
| 同上(host extapi 路径同样有此隐患) |
| 修改点3 |
`LOG_FILE`
定义移至 flock 之前 +
`mkdir -p`
| 修复
`tee: ''`
报错 |
| 释放锁 | kill 持锁的旧 malan(PID 70515) | 解除当前死锁,脚本自身会重新拉起 malan |
### 2.2 代码修改点
#### 变更1: start_malan_service 关闭锁 fd
```
bash
# 修改前
# 加载环境变量,然后启动malan
bash
-c
"source /etc/profile && ./malan"
&
# 修改后
# 关闭锁文件描述符,防止子进程继承导致锁无法释放
exec
200>&-
# 加载环境变量,然后启动malan
bash
-c
"source /etc/profile && ./malan"
&
```
#### 变更2: restart_host_extapi_service 关闭锁 fd
```
bash
# 修改前
bash
-c
"source /etc/profile && ./run.sh"
&
# 修改后
# 关闭锁文件描述符,防止子进程继承导致锁无法释放
exec
200>&-
bash
-c
"source /etc/profile && ./run.sh"
&
```
#### 变更3: LOG_FILE 定义前置
```
bash
# 修改前(flock 在前,LOG_FILE 在后)
LOCK_FILE
=
"/tmp/ujava2-startup.lock"
exec
200>
"
$LOCK_FILE
"
flock
-n
200
||
{
...
tee
-a
"
$LOG_FILE
"
...
}
LOG_FILE
=
"/data/logs/ujava2-service-manager.log"
# 修改后(与仓库版一致)
LOCK_FILE
=
"/tmp/ujava2-startup.lock"
LOG_FILE
=
"/data/logs/ujava2-service-manager.log"
# 确保日志目录存在
mkdir
-p
"
$(
dirname
"
$LOG_FILE
"
)
"
2>/dev/null
exec
200>
"
$LOCK_FILE
"
flock
-n
200
||
{
echo
"
$(
date
'+%Y-%m-%d %H:%M:%S'
)
[WARNING] 另一个实例正在运行,退出"
|
tee
-a
"
$LOG_FILE
"
exit
1
}
```
---
## 3. 执行计划
### 3.1 修改清单
-
[
x
]
步骤1: 备份服务器脚本为
`ujava2-startup.sh.bak.20260901`
-
[
x
]
步骤2: 变更1 —
`start_malan_service`
加
`exec 200>&-`
-
[
x
]
步骤3: 变更2 —
`restart_host_extapi_service`
加
`exec 200>&-`
-
[
x
]
步骤4: 变更3 —
`LOG_FILE`
前置 +
`mkdir -p`
-
[
x
]
步骤5:
`bash -n`
语法校验(SYNTAX_OK)
-
[
x
]
步骤6: kill 持锁 malan(PID 70515)释放死锁
-
[
x
]
步骤7: 手动执行脚本验证完整流程
-
[
x
]
步骤8: 等待 cron 自动触发验证恢复
-
[
x
]
步骤9: 问题处理文档 + 本计划执行文档输出
### 3.2 验证结果(2026-09-01 15:46 ~ 15:54)
| 验证项 | 结果 |
|--------|------|
| 手动执行脚本 | ✅ EXIT_CODE=0,完整跑通 API 检查、服务自愈、6060 监测 |
| 新 malan 进程 | ✅ PID 128794 正常运行,
**不再持有 /tmp/ujava2-startup.lock**
|
| cron 自动触发 | ✅ 15:51、15:54 两轮日志正常完整执行(
`6060端口正在监听`
),无 WARNING 退出 |
| 锁释放 | ✅ cron 运行间隙
`fuser /tmp/ujava2-startup.lock`
无输出 |
| tee 报错 | ✅ 修复后 cron 日志无
`tee: '': 没有那个文件或目录`
|
### 3.3 回滚方案
```
bash
# 若修复后异常,恢复备份
cp
/data/services/scripts/ujava2-startup.sh.bak.20260901 /data/services/scripts/ujava2-startup.sh
```
---
## 4. 优化功能回填
| 优化项 | 状态 | 说明 |
|--------|------|------|
| start_malan_service 关闭锁 fd | 已实施 |
`exec 200>&-`
防止 malan 继承 flock 锁 |
| restart_host_extapi_service 关闭锁 fd | 已实施 | 同上,消除 host extapi 路径同类隐患 |
| LOG_FILE 定义前置 | 已实施 | 修复 flock 分支
`tee -a ""`
报错 |
| 死锁释放与验证 | 已实施 | kill 旧 malan + 手动执行 + 两轮 cron 确认自动恢复 |
| 预防规范 | 已沉淀 | flock 脚本后台启动守护进程前必须关闭锁 fd(详见问题处理文档 3.5 预防措施) |
| 仓库/服务器脚本对齐 | 待实施 | 服务器剩余差异(RETRY_INTERVAL 20/30、stop_service kill 逻辑)为旧版配置差异,非本次问题范围,待后续部署流程统一 |
---
*文档生成时间: 2026-09-01*
自动化部署脚本/arm架构/临时目录/fastdfs_start_问题分析报告.md
0 → 100644
浏览文件 @
1e8018f7
# fastdfs_start.sh 问题分析报告
> 分析对象:`自动化部署脚本/arm架构/临时目录/fastdfs_start.sh`
> 分析日期:2026-07-13
> 结论:**存在容器重启后 storage / tracker 不启动的隐患**,且不止一处会触发该故障。
---
## 一、结论先行
脚本最大的风险点是
`fdfs_trackerd ... restart`
/
`fdfs_storaged ... restart`
的
**重启语义在容器环境(PID1、无 init 系统)下不可靠**
。在镜像首次启动、容器重启、pid 残留等多种场景下,
`restart`
的
`stop`
阶段行为不确定,会导致 tracker / storage 进程没起来,而脚本又没有错误检查与守护机制,最终表现为:
-
容器进程在跑(
`tail -f /dev/null`
保活),但 FastDFS 服务全挂;
-
或脚本中途退出,容器直接
`Exited`
,配合
`--restart=always`
陷入重启死循环。
---
## 二、问题清单(按严重程度分级)
### 🔴 P0 严重 —— 直接导致重启后服务起不来
#### 问题 1:`restart` 语义不可靠(最可能的根因)
**位置:**
第 35、38 行
```
bash
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
```
`fdfs_trackerd`
/
`fdfs_storaged`
实际是 wrapper 脚本,
`restart`
=
`stop`
+
`start`
。问题在于:
| 场景 | stop 阶段行为 | 后果 |
|------|--------------|------|
|
**镜像首次启动**
(进程从未运行) | 无 pid 文件,很多版本返回非零退出 |
`start`
不一定被执行 → tracker 没起来 |
|
**容器重启后**
(PID 命名空间全新,进程已死,pid 文件可能残留) | 尝试 kill 不存在的 pid | 部分版本静默继续,部分版本直接退出 |
|
**配置持久化(volume)**
| 残留的旧 pid 指向已死进程 | kill 失败,行为不确定 |
**修复方向:**
用
`start`
替代
`restart`
,并做幂等兜底:
```
bash
fdfs_trackerd /etc/fdfs/tracker.conf stop 2>/dev/null
||
true
fdfs_trackerd /etc/fdfs/tracker.conf start
fdfs_storaged /etc/fdfs/storage.conf stop 2>/dev/null
||
true
fdfs_storaged /etc/fdfs/storage.conf start
```
---
#### 问题 2:脚本中途任一步失败会直接退出,导致容器 Exited
**位置:**
第 35、38、41、45 行
```
bash
/usr/local/nginx/sbin/nginx
# 第41行:若启动失败(端口占用/配置错),脚本退出
tail
-f
/dev/null
# 第45行:容器靠它保活,前面挂了到不了这里
```
`tail -f /dev/null`
是容器内
**唯一的 PID1 守护进程**
。tracker / storage / nginx 任一启动失败退出,脚本立即终止 → 容器
`Exited`
。配合
`--restart=always`
会反复重启失败,尤其 nginx 端口冲突会陷入死循环。
---
### 🟠 P1 高 —— 健壮性与幂等性
#### 问题 3:全程无 `set -e`,无单步错误检查
脚本缺少
`set -euo pipefail`
,每个关键命令(sed / fdfs_trackerd / nginx)执行后都没判断
`$?`
。
-
第 28-32 行的 sed 替换若失败(配置文件路径在某版本镜像中不同),tracker / storage 会带旧 IP 启动;
-
storage 连不上 tracker,但脚本继续往下跑,最后仍打印
`fastdfs start success`
——
**假性成功**
,排查时极易误判。
---
#### 问题 4:sed 替换"一次成型",不可重入(容器二次启动出问题)
**位置:**
第 6、28-30 行
```
bash
old_fastdfs_ipaddr
=
"192.168.0.165"
sed
-i
"s/
$old_fastdfs_ipaddr
/
$new_fastdfs_ipaddr
/g"
/etc/fdfs/storage.conf
```
| 启动阶段 | 配置文件内容 | sed 行为 |
|---------|-------------|---------|
| 第一次启动 |
`192.168.0.165`
| 替换成
`$FASTDFS_IPADDR`
✅ |
| 重启后再跑(配置未重置) |
`$FASTDFS_IPADDR`
(旧 IP 已被替换) |
`192.168.0.165`
不存在 →
**啥也不替换**
❌ |
**更严重的副作用:**
storage.conf 的
`tracker_server`
一旦被替换成具体 IP,storage 会把当前 IP 当成 tracker 地址。若
`FASTDFS_IPADDR`
传的是 storage 自己的 IP(常见用法),storage 会找不到 tracker,进程起来但持续报:
```
tracker server can't be reached
```
最终 storage 不可用。
**修复方向:**
替换整行配置(用占位符 / 行匹配),而非替换"上一次的 IP":
```
bash
sed
-i
"s/^tracker_server=.*/tracker_server=
$FASTDFS_IPADDR
:22122/"
/etc/fdfs/storage.conf
sed
-i
"s/^tracker_server=.*/tracker_server=
$FASTDFS_IPADDR
:22122/"
/etc/fdfs/mod_fastdfs.conf
```
---
#### 问题 5:nginx 启动无幂等
**位置:**
第 41 行
```
bash
/usr/local/nginx/sbin/nginx
```
-
容器重启场景(PID1 重置、nginx 不在):此条正常;
-
但脚本被手动
`docker exec`
再跑一次:nginx 已在运行 → 端口占用退出。
**修复方向:**
加幂等判断:
```
bash
if
pgrep
-x
nginx
>
/dev/null
;
then
nginx
-s
reload
else
nginx
fi
```
---
### 🟡 P2 中 —— 规范性问题
#### 问题 6:`exit` 无状态码
**位置:**
第 17 行
```
bash
exit
# 没指定退出码,依赖上一条 echo 的返回值(0)
```
参数校验失败应明确
`exit 1`
,让 dockerd 能识别失败、触发重启策略。
---
#### 问题 7:`WEB_PORT` 校验形同虚设
**位置:**
第 22-26 行 + 第 32 行
第 22-26 行只在
`WEB_PORT`
为空时打印 WARN,但第 32 行 sed 仍会用
**空串**
替换:
```
bash
sed
-i
"s/
$old_web_port
/
$new_web_port
/g"
/usr/local/nginx/conf/nginx.conf
# new_web_port 为空时,会把 nginx.conf 里所有 8888 删成空 → 监听配置损坏
```
**修复方向:**
为空时用默认值:
```
bash
new_web_port
=
"
${
WEB_PORT
:-
8888
}
"
```
---
#### 问题 8:`mkdir -p /home/fastdfs` 无意义
**位置:**
第 3 行
创建的目录后续没被使用。FastDFS 数据目录在 storage.conf 的
`store_path0`
(通常是
`/fastdfs/storage`
)。要么删除该行,要么应与 storage.conf 的 store_path 保持一致。
---
## 三、针对核心问题的直接回答
> **有没有可能存在容器重启后出现 storage 和 tracker 服务没启动的问题?**
**有可能,且概率不低。**
有两条主要重现路径:
### 路径 A:restart 语义路径(最常见)
```
容器重启
→ 脚本重跑
→ fdfs_trackerd ... restart 的 stop 阶段找不到活动进程/pid,行为异常
→ tracker 没起来
→ storage 因连不上 tracker 退出
→ nginx 可能起来但后端无 storage
→ 现象:容器在跑,文件上传全失败(假性运行)
```
### 路径 B:sed 幂等路径(配置持久化场景)
```
容器重启(配置目录持久化)
→ 脚本重跑
→ sed 替换不到占位符(已被上次替换成实 IP)
→ 配置错位 / tracker_server 指向错误 IP
→ storage 起不来
```
---
## 四、重启后排查步骤
```
bash
# 1. 看进程在不在
docker
exec
-it
<容器名> ps
-ef
|
grep
fdfs
# 2. 看 tracker_server 配置是否正确
docker
exec
-it
<容器名>
grep
tracker_server /etc/fdfs/storage.conf
# 3. 看启动日志有没有关键报错
docker logs <容器名> |
grep
-E
"tracker|storage|can't be reached|pid"
```
**典型故障日志:**
-
`tracker server can't be reached`
→ storage 连不上 tracker(路径 A/B)
-
`file pid ... not exist`
或 stop 报错 → restart 语义问题(路径 A)
-
容器状态
`Exited (1)`
+ 反复重启 → 脚本中途退出(问题 2)
---
## 五、建议修复版脚本
```
bash
#!/bin/bash
set
-euo
pipefail
# ===== 参数处理(带默认值)=====
new_fastdfs_ipaddr
=
"
${
FASTDFS_IPADDR
:?[ERROR]
FASTDFS_IPADDR is blank, please pass via -e FASTDFS_IPADDR=x.x.x.x
}
"
new_web_port
=
"
${
WEB_PORT
:-
8888
}
"
echo
"[INFO] FASTDFS_IPADDR=
${
new_fastdfs_ipaddr
}
"
echo
"[INFO] WEB_PORT=
${
new_web_port
}
"
mkdir
-p
/home/fastdfs
# ===== 配置替换(用行匹配,幂等可重入)=====
sed
-i
"s/^tracker_server=.*/tracker_server=
${
new_fastdfs_ipaddr
}
:22122/"
/etc/fdfs/client.conf
sed
-i
"s/^tracker_server=.*/tracker_server=
${
new_fastdfs_ipaddr
}
:22122/"
/etc/fdfs/storage.conf
sed
-i
"s/^tracker_server=.*/tracker_server=
${
new_fastdfs_ipaddr
}
:22122/"
/etc/fdfs/mod_fastdfs.conf
# WEB_PORT 用占位符替换更安全;此处保留原 8888 行匹配
sed
-i
"s/^.*listen .*8888.*;/ listen
${
new_web_port
}
;/"
/usr/local/nginx/conf/nginx.conf
# ===== 启动 FastDFS(stop 兜底 + start,避免 restart 语义问题)=====
echo
"start trackerd"
fdfs_trackerd /etc/fdfs/tracker.conf stop 2>/dev/null
||
true
fdfs_trackerd /etc/fdfs/tracker.conf start
echo
"start storage"
fdfs_storaged /etc/fdfs/storage.conf stop 2>/dev/null
||
true
fdfs_storaged /etc/fdfs/storage.conf start
# ===== 启动 nginx(幂等)=====
echo
"start nginx"
if
pgrep
-x
nginx
>
/dev/null 2>&1
;
then
nginx
-s
reload
else
nginx
fi
echo
'fastdfs start success........'
# ===== 容器保活 =====
tail
-f
/dev/null
```
---
## 六、问题汇总表
| 编号 | 等级 | 问题 | 位置 | 影响 |
|------|------|------|------|------|
| 1 | 🔴 P0 |
`restart`
语义不可靠 | 35、38 行 | 重启后 tracker/storage 不启动(假性运行) |
| 2 | 🔴 P0 | 脚本中途失败会退出,容器 Exited | 35-45 行 | 任一服务失败 → 容器退出 / 死循环重启 |
| 3 | 🟠 P1 | 无
`set -e`
,无错误检查 | 全局 | 假性成功,难以排查 |
| 4 | 🟠 P1 | sed 替换不可重入 | 6、28-30 行 | 重启后配置错位 / tracker_server 指向错 |
| 5 | 🟠 P1 | nginx 启动无幂等 | 41 行 | 手动重跑脚本失败 |
| 6 | 🟡 P2 |
`exit`
无状态码 | 17 行 | dockerd 无法识别失败 |
| 7 | 🟡 P2 |
`WEB_PORT`
校验形同虚设 | 22-32 行 | 不传参时 nginx.conf 监听损坏 |
| 8 | 🟡 P2 |
`mkdir /home/fastdfs`
无意义 | 3 行 | 死代码 / 易混淆 |
自动化部署脚本/arm架构/临时目录/fastdfs重启问题_业主说明_两版.md
0 → 100644
浏览文件 @
1e8018f7
# FastDFS 容器重启后服务不启动 —— 业主说明(两版)
> 场景:业主反馈 FastDFS 文件存储服务在容器/宿主机重启后出现不可用,需要给出原因解释。
> 用途:根据沟通对象选择对应版本。
---
## 版本一:专业版(面向技术负责人 / 运维 / 验收方)
### 1. 问题概述
FastDFS 部署采用 Docker 容器方式,容器内启动脚本
`fastdfs_start.sh`
在启动 tracker、storage、nginx 三个核心进程时使用了
`restart`
参数。在容器重启场景下,该参数的行为不可靠,存在导致 tracker / storage 进程未正常拉起的风险,最终表现为文件上传/下载功能不可用。
### 2. 根因分析
#### 2.1 `restart` 语义在容器环境下不可靠
FastDFS 的
`fdfs_trackerd`
/
`fdfs_storaged`
实际是 wrapper 脚本,
`restart`
指令等价于
`stop`
+
`start`
两步操作。在容器重启场景下:
-
容器重启后,容器内进程命名空间是全新的,tracker / storage 进程均不在运行;
-
但
`/etc/fdfs/`
下的 pid 文件可能仍然残留(指向已不存在的进程);
-
`stop`
阶段尝试停止一个不存在的 pid,在部分 FastDFS 版本中会返回非零退出码;
-
wrapper 脚本对
`stop`
失败的处理不一致,可能导致后续
`start`
步骤未执行;
-
结果:tracker 未启动 → storage 无法连接 tracker → storage 退出 → FastDFS 服务整体不可用。
#### 2.2 启动脚本缺少错误处理与进程守护
-
脚本未启用
`set -e`
,各启动命令执行后未做返回值检查,无法在单步失败时中止并告警;
-
容器依赖
`tail -f /dev/null`
作为 PID1 保活进程,一旦 tracker / storage / nginx 任一启动失败导致脚本退出,容器会进入
`Exited`
状态;
-
在配置
`--restart=always`
策略下,若失败由端口冲突等持续性原因导致,容器会陷入反复重启的死循环。
#### 2.3 配置替换逻辑不可重入
脚本通过
`sed`
将配置文件中硬编码的旧 IP 替换为
`FASTDFS_IPADDR`
环境变量值。在配置目录持久化(volume 挂载)的场景下,容器二次启动时旧 IP 已被替换,sed 无可替换内容,可能导致
`tracker_server`
配置错位,进一步导致 storage 无法连接 tracker。
### 3. 影响范围
| 影响项 | 说明 |
|--------|------|
| 受影响功能 | 文件上传、下载、访问(nginx 代理) |
| 触发条件 | 容器重启 / 宿主机重启后容器自启 |
| 故障特征 | 容器状态可能为 Running(假性运行)或 Exited |
| 数据影响 | 不影响已存储文件数据本身,仅影响服务可用性 |
### 4. 修复方案
1.
将
`restart`
改为
`stop`
(容错)+
`start`
,避免 stop 失败阻断 start;
2.
启用
`set -euo pipefail`
,单步失败立即退出并返回非零状态码,便于 dockerd 识别与重启;
3.
配置替换改用"行匹配替换"而非"IP 替换",保证脚本幂等可重入;
4.
nginx 启动增加进程存在性判断,避免重复启动报错;
5.
参数校验失败时明确返回
`exit 1`
。
### 5. 验证方式
-
容器重启后执行
`docker exec <容器> ps -ef | grep fdfs`
,确认 tracker / storage 进程存在;
-
检查
`docker logs`
是否出现
`tracker server can't be reached`
或 pid 相关报错;
-
通过实际上传文件验证端到端可用性。
---
## 版本二:概要通俗易懂版(面向项目业主 / 业务方 / 非技术决策者)
### 问题说明
文件存储服务(FastDFS)在服务器重启或容器重启后,偶尔会出现文件上传下载暂时用不了的情况。这个问题我们已经找到了原因,修复方案也明确了。
### 请放心
**所有已存储的文件都是安全的,不会丢失。**
只是重启后服务需要重新启动一下,目前这个"自动启动"的流程有一处小瑕疵,导致偶尔没启动成功。修复后就能稳定恢复。
### 问题原因(简单理解)
可以把它理解成"开机自启的手势顺序有点问题"。
容器启动时会依次启动三个程序,之前用的是"
**重启**
"指令。重启后程序本来都没在运行,但脚本先去执行了"停止"这一步,因为找不到运行中的程序就卡住了,后面的"启动"步骤就没执行到。
打个比方:就像一个人去按电梯,他先按了"关门"再按"开门",但电梯本来就是关着的,关门键按了没反应,他就走了,没按开门键 —— 门一直关着进不去。
### 范围说明
-
**受影响的功能**
:只涉及文件上传和下载,其他功能不受影响;
-
**触发条件**
:仅发生在服务器重启或容器重启后,正常运行时不受影响;
-
**数据安全**
:
**已存储的所有文件完好无损**
,问题修复后即可正常访问;
-
**不是硬件故障**
:不是服务器坏了,也不是硬盘坏了,只是启动脚本的一处逻辑需要调整。
### 修复方案
把启动脚本里的"重启"指令换成"直接启动",并加上简单的容错判断。改动很小,修复后重启就不会再出现这个问题了。
### 验证方式
修复后重启一次容器,上传一个测试文件,确认能正常上传和下载,就说明修复完成。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论