📖 前言

在 Kubernetes 环境中部署应用时,配置文件路径不匹配是一个常见但容易被忽视的问题。本文记录了一次真实的排查和解决过程,涉及 Vault 集成、ConfigMap 挂载、以及容器工作目录的配置。

🎯 问题背景

应用架构

  • 服务类型:ASR(自动语音识别)实时转录服务
  • 部署方式:Kubernetes Deployment
  • 配置管理:HashiCorp Vault + ConfigMap
  • 容器镜像:基于 Python 3.11,工作目录 /app

问题现象

服务部署后无法正常启动,日志中频繁出现:

FileNotFoundError: LiveKit 配置文件不存在: configs/livekit_config.ini
FileNotFoundError: ASR 配置文件不存在: configs/config_qwen.ini

🔍 问题分析

1. 代码中的配置文件路径

通过代码审查,发现应用使用相对路径读取配置文件:

# asr_service_main.py
config_path = Path('configs/service_config.ini')

# src/api/asr_control_routes.py
livekit_config_file = 'configs/livekit_config.ini'

# src/services/asr_service_manager.py
livekit_config_file = 'configs/livekit_config.ini'

关键点:代码使用相对路径 configs/xxx.ini,相对于工作目录。

2. Dockerfile 中的工作目录

WORKDIR /app

结论:代码期望配置文件在 /app/configs/ 目录下。

3. Kubernetes 部署配置

原始配置(错误)
# deployment.yaml
containers:
  - name: ai-speech2txt-realtime
    volumeMounts:
      - name: shared-data
        mountPath: /etc/speech2txt/configs/config_qwen.ini
        subPath: config_qwen.ini
      - name: shared-data
        mountPath: /etc/speech2txt/configs/livekit_config.ini
        subPath: livekit_config.ini
      - name: shared-data
        mountPath: /etc/speech2txt/configs/service_config.ini
        subPath: service_config.ini
Vault Agent 配置(错误)
# vault-inject.yaml
"template" = {
  "destination" = "/etc/speech2txt/configs/config_qwen.ini"
  "source" = "/data/origin/config_qwen.ini"
  ...
}

问题根源:

  • 配置文件挂载到 /etc/speech2txt/configs/
  • 代码期望在 /app/configs/
  • 路径不匹配导致文件找不到

💡 解决方案

方案对比

方案优点缺点推荐度
方案 A:修改挂载路径简单直接,符合代码期望需要修改部署配置⭐⭐⭐⭐⭐
方案 B:创建符号链接不需要修改挂载路径需要修改启动命令,增加复杂度⭐⭐
方案 C:修改代码路径不需要修改部署配置需要修改代码,影响其他部署方式⭐

最终选择:方案 A - 修改挂载路径

实施步骤

1. 修改 Deployment 挂载路径
# deployment.yaml
containers:
  - name: ai-speech2txt-realtime
    volumeMounts:
      - name: shared-data
        mountPath: /app/configs/config_qwen.ini  # ✅ 改为 /app/configs/
        subPath: config_qwen.ini
      - name: shared-data
        mountPath: /app/configs/livekit_config.ini  # ✅ 改为 /app/configs/
        subPath: livekit_config.ini
      - name: shared-data
        mountPath: /app/configs/service_config.ini  # ✅ 改为 /app/configs/
        subPath: service_config.ini
2. 修改 Init Container 挂载路径
# deployment.yaml
initContainers:
  - name: vault-token-init
    volumeMounts:
      - name: shared-data
        mountPath: /app/configs/  # ✅ 改为 /app/configs/
3. 修改 Vault Agent 目标路径
# vault-inject.yaml
"template" = {
  "destination" = "/app/configs/config_qwen.ini"  # ✅ 改为 /app/configs/
  "source" = "/data/origin/config_qwen.ini"
  "left_delimiter" = "{{"
  "right_delimiter" = "}}"
}

"template" = {
  "destination" = "/app/configs/livekit_config.ini"  # ✅ 改为 /app/configs/
  "source" = "/data/origin/livekit_config.ini"
  ...
}

"template" = {
  "destination" = "/app/configs/service_config.ini"  # ✅ 改为 /app/configs/
  "source" = "/data/origin/service_config.ini"
  ...
}

🔄 完整工作流程

配置渲染流程

┌─────────────────────────────────────────────────────────────┐
│ 1. Init Container (Vault Agent)                            │
│    ├─ 从 ConfigMap 读取模板: /data/origin/xxx.ini         │
│    ├─ 连接 Vault 并渲染模板                                │
│    └─ 写入渲染后的文件: /app/configs/xxx.ini              │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Main Container                                           │
│    ├─ 从 /app/configs/xxx.ini 读取配置文件                 │
│    ├─ 代码使用相对路径: configs/xxx.ini                    │
│    └─ 工作目录: /app → 实际路径: /app/configs/xxx.ini     │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 路径匹配成功 ✅                                          │
│    - 代码期望: configs/xxx.ini (相对于 /app)               │
│    - 实际路径: /app/configs/xxx.ini                        │
│    - 匹配结果: ✅ 成功                                      │
└─────────────────────────────────────────────────────────────┘

🚀 部署操作

重要提示

⚠️ 仅重启 Pod 不会应用配置修改!

修改 YAML 文件后,必须重新应用(apply)配置,Kubernetes 才会更新运行中的资源。

部署命令

# 方法 1: 使用 kubectl apply(推荐)
cd speech2txt-realtime-k8s/test/manifests
kubectl apply -k .

# 方法 2: 强制重启 Deployment
kubectl rollout restart deployment/ai-speech2txt-realtime -n ai

验证步骤

1. 检查 Pod 状态
kubectl get pods -n ai -l app=ai-speech2txt-realtime

期望结果:Pod 状态为 Running

2. 验证配置文件路径
POD_NAME=$(kubectl get pods -n ai -l app=ai-speech2txt-realtime -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $POD_NAME -n ai -- ls -la /app/configs/

期望结果:

config_qwen.ini
livekit_config.ini
service_config.ini
3. 检查配置文件内容
kubectl exec -it $POD_NAME -n ai -- cat /app/configs/config_qwen.ini

期望结果:配置文件内容已渲染(没有 {{ with secret ... }} 标签)

4. 检查服务日志
kubectl logs $POD_NAME -n ai

期望结果:

  • ✅ 没有 “配置文件不存在” 的错误
  • ✅ 服务正常启动并监听 9133 端口
5. 健康检查
kubectl exec -it $POD_NAME -n ai -- curl http://localhost:9133/health

期望结果:

{
  "status": "healthy",
  "service": "asr-service",
  "version": "2.0.0",
  "active_services": 0
}

🎓 经验总结

1. 路径配置原则

  • 相对路径 vs 绝对路径:

    • 代码中使用相对路径时,必须明确工作目录
    • 部署配置中的挂载路径必须与代码期望的路径一致
  • 工作目录的重要性:

    • Dockerfile 中的 WORKDIR 决定了相对路径的基准
    • 部署时必须确保挂载路径与工作目录匹配

2. Vault 集成注意事项

  • 模板渲染位置:

    • Vault Agent 的 destination 必须与容器挂载路径一致
    • Init Container 和 Main Container 的挂载路径必须匹配
  • 模板语法:

    • {{ with secret ... }} 会触发 Vault 连接,但不一定使用密钥值
    • 真正使用密钥值需要:{{ .Data.data.xxx }}

3. Kubernetes 配置更新

  • 配置更新机制:

    • 修改 YAML 文件不会自动更新运行中的资源
    • 必须使用 kubectl apply 或删除 Pod 触发重建
  • 滚动更新:

    • Deployment 的 rollingUpdate 策略确保零停机更新
    • maxUnavailable: 0 确保至少有一个 Pod 可用

4. 排查技巧

  • 分层排查:

    1. 检查 Pod 状态和事件
    2. 验证文件系统挂载
    3. 检查配置文件内容
    4. 查看应用日志
  • 常用命令:

    # 查看 Pod 详细信息
    kubectl describe pod <pod-name> -n <namespace>
    
    # 查看挂载信息
    kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Mounts:"
    
    # 进入容器调试
    kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
    

🔧 其他发现的问题

在排查过程中,还发现了以下问题并一并修复:

1. HPA 和 Deployment replicas 不一致

问题:

  • Deployment: replicas: 1
  • HPA: minReplicas: 2

影响:HPA 会立即将副本数调整为 2,但初始部署时会有短暂不一致。

解决:统一为 replicas: 2(或根据实际需求调整)

2. kustomization.yaml 缺少 HPA

问题:kustomization.yaml 中没有包含 hpa.yaml。

影响:HPA 不会被自动应用。

解决:添加 deploy/hpa.yaml 到 resources 列表。

📚 参考资料

🎯 最佳实践建议

1. 配置文件路径管理

  • ✅ 使用环境变量配置路径(便于不同环境切换)
  • ✅ 在代码中提供路径验证和清晰的错误信息
  • ✅ 在部署文档中明确说明路径要求

2. 配置更新流程

  • ✅ 使用 GitOps 工具(如 ArgoCD)自动同步配置
  • ✅ 配置变更前进行代码审查
  • ✅ 配置更新后进行验证测试

3. 监控和告警

  • ✅ 监控配置文件加载失败的情况
  • ✅ 设置 Pod 启动失败的告警
  • ✅ 记录配置变更历史

📝 总结

这次问题排查和解决过程涉及了:

  1. 问题定位:通过日志和代码审查找到根本原因
  2. 方案选择:对比多种方案,选择最合适的
  3. 配置修复:修改 Deployment 和 Vault 配置
  4. 验证测试:确保修复后服务正常运行

关键教训:

  • 路径一致性:代码、Dockerfile、Kubernetes 配置中的路径必须一致
  • 配置更新:修改 YAML 后必须重新 apply
  • 分层验证:从 Pod 状态到文件系统再到应用日志,逐层验证

希望这篇文章能帮助遇到类似问题的开发者快速定位和解决问题。

更多推荐