写在前面
在企业级微服务架构的落地过程中,部署往往是最让人头疼的环节之一。特别是当你的客户要求"断网部署"、“一键安装”、“版本可追溯"时,传统的手动部署或CI/CD流水线方案就显得力不从心了。
这篇文章记录了我为某企业级微服务系统设计的一套离线自动化部署脚本——从背景痛点到架构设计,再到核心实现细节。希望能给同样面临类似场景的同行一些参考。
背景:为什么需要离线自动化部署?
痛点分析
我们团队负责的系统是基于一个成熟的企业级微服务框架二次开发的,整套系统包含:
- 13个核心微服务(注册中心、网关、认证、权限等)
- 多种中间件(MySQL、MongoDB、Redis、MinIO)
- ONLYOFFICE文档编辑服务
每次给客户现场部署时,我们都要面对这些问题:
- 耗时漫长:手动部署整套系统需要数小时,而且容易出错
- 网络限制:很多客户现场是内网环境,无法访问外网镜像仓库
- 版本混乱:手动记录版本信息容易遗漏,出问题难以追溯
- 环境差异:开发、测试、生产环境配置各不相同,切换困难
设计目标
基于这些问题,我确定了设计目标:
离线部署 + 一键执行 + 版本可追溯 + 多环境支持
架构设计:模块化脚本职责划分
整体方案采用Shell脚本实现(约450行核心代码),核心思路是职责分离、配置集中。
目录结构
deploy/
├── deploy.sh # 主入口:环境准备 + 数据库初始化
├── service.sh # K8s部署核心逻辑(install/uninstall)
├── initChart.sh # Helm Chart生成 + 版本信息SQL
├── services.sh # 服务列表定义(版本化管理)
├── vars.sh # 环境变量集中管理
├── func.sh # 公共函数(字符串处理、分组解析)
├── k8s/ # K8s资源文件(PV/PVC/Deployment)
├── values/ # Helm Values配置(多环境支持)
├── mysql/ # 数据库初始化SQL
├── images/ # Docker镜像包(离线部署用)
└── charts/ # Helm Chart包(离线部署用)
职责划分
| 脚本 | 职责 | 关键功能 |
|---|---|---|
deploy.sh |
主编排 | 依赖检查、流程控制、数据库初始化 |
service.sh |
K8s操作 | 服务部署/卸载、镜像导入、Chart生成 |
initChart.sh |
Chart管理 | 模板渲染、版本SQL生成 |
services.sh |
服务清单 | 版本化服务列表定义 |
vars.sh |
配置中心 | 环境变量集中管理 |
func.sh |
工具库 | 正则解析、日志输出等通用函数 |
这种设计让每个脚本职责单一,修改某个环节不会牵一发动全身。
核心实现:关键技术与设计决策
1. 版本化服务管理
服务命名采用统一的版本格式:
{服务名}-{日期}-{构建号}-{分支}
例如:app-gateway-2023.9.5-152505-master
在 services.sh 中定义服务列表:
# services.sh
services[0]="app-register-2023.5.29-095954-master"
services[1]="app-gateway-2023.9.5-152505-master"
services[2]="app-oauth-2023.8.15-141936-master"
services[3]="app-iam-2023.7.28-105949-master"
# ... 共13个服务
通过正则解析服务名和版本:
if [[ $chartName =~ ^(.*)-([0-9]{4}\.[0-9]{1,2}.[0-9]{1,2}-[0-9]+-.*)$ ]]; then
serviceName=${BASH_REMATCH[1]} # 提取服务名称
serviceImage=${BASH_REMATCH[2]} # 提取版本号
fi
这样做的好处是版本信息一目了然,新增服务只需添加一行配置。
2. 多环境配置支持
环境配置采用"默认值 + 环境覆盖"的模式:
# vars.sh(默认配置)
declare -g VERSION="1.0.0"
declare -g HOST="${SERVER_IP}"
declare -g NAMESPACE="app-prod"
declare -g MYSQL_HOST="${DB_IP}"
declare -g MYSQL_USER="app_user"
# vars/vars_prod.sh(生产环境覆盖)
# vars/vars_uat.sh(UAT环境覆盖)
切换环境只需传递不同的配置文件:
./deploy.sh vars_prod.sh # 生产环境部署
./deploy.sh vars_uat.sh # UAT环境部署
3. Helm Template + kubectl 方案
这是一个关键的架构决策。我没有使用标准的 helm install,而是采用了 helm template + kubectl apply 的方案:
# 生成YAML文件
helm template "$serviceName" "$localChartPath" \
--values "$customValuesFile" \
--namespace "$NAMESPACE" > "$CHART_DIR/$serviceName.yaml"
# 应用YAML
kubectl apply -f "$CHART_DIR/$serviceName.yaml" -n $NAMESPACE
为什么这样做?
| 对比项 | helm install | helm template + kubectl |
|---|---|---|
| YAML可见性 | 隐藏 | 可检查、可版本管理 |
| 依赖Helm Release | 是 | 否 |
| 离线环境友好度 | 一般 | 更好 |
| 故障排查 | 需要helm命令 | 直接看YAML |
| 回滚机制 | Helm内置 | kubectl rollout |
对于生产环境和离线部署场景,YAML可检查的优势太重要了——出问题时,你可以直接看文件内容定位问题。
4. NFS共享存储配置
有状态服务需要持久化存储,我采用了NFS方案:
# nfs-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
capacity:
storage: 50Gi
nfs:
path: /data/nfs
server: ${NFS_SERVER_IP}
脚本中自动配置NFS服务:
mkdir -p "$NFS_PATH"
cp -f ./k8s/exports /etc/
exportfs -ra
systemctl restart nfs-server
5. 数据库初始化自动化
数据库初始化分为两步:官方工具 + 自定义SQL:
# 解压官方初始化工具
tar -xf "./mysql/init-tool.tgz" -C "./mysql"
# 调用初始化脚本
sh -c "cd ./mysql/init-tool/ && ./database-init.sh"
# 执行自定义SQL
mysql -u "$MYSQL_USER" app_db < ./mysql/init_app.sql
6. 版本信息自动生成
每次部署都会自动在数据库中记录版本信息:
# initChart.sh
sqlList="update hpfm_release_history set enable = 0 where enable = 1;\n"
for service in services; do
sqlList+="replace into hpfm_release_history
set release = '$VERSION',
svc_name = '$serviceName',
svc_version = '$serviceImage',
enable = 1;\n"
done
echo -e $sqlList > "./mysql/version.sql"
这样运维团队可以随时查询部署历史,方便问题追溯。
部署流程:完整链路
整个部署流程可以抽象为以下四个阶段:
┌─────────────────────────────────────────────────────────────────┐
│ 1. 环境准备 │
│ - 检查依赖:MySQL/Redis/MongoDB/K8s/Docker │
│ - 用户确认 │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 数据库初始化 │
│ - 解压并调用官方初始化工具 │
│ - 执行自定义SQL │
│ - 用户确认完成 │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. 配置注入 │
│ - 从 vars/*.sh 读取环境变量 │
│ - 替换 values/*.yaml 中的变量占位符 │
│ - 生成 Helm Chart 文件 │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. K8s部署 │
│ - 创建命名空间 │
│ - 配置NFS存储 │
│ - 导入Docker镜像 │
│ - 遍历部署13个服务 │
│ - 生成版本SQL并执行 │
└─────────────────────────────────────────────────────────────────┘
执行示例
# 1. 进入部署目录
cd /opt/deploy
# 2. 执行部署脚本
./deploy.sh vars_prod.sh
# 脚本会依次:
# ✓ 检查依赖服务
# ✓ 初始化数据库
# ✓ 导入Docker镜像
# ✓ 部署所有微服务
# ✓ 记录版本信息
成果与总结
部署效果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 部署时间 | 2-4小时 | 10-15分钟 |
| 人工干预 | 大量手动操作 | 仅需确认参数 |
| 成功率 | 约70%(人为错误多) | 接近100% |
| 版本追溯 | 依赖手工记录 | 数据库自动记录 |
技术亮点
- 模块化设计:6个脚本职责分离,维护成本低
- 版本化管理:服务版本自动解析、数据库记录,可追溯
- 多环境支持:一套脚本、多套配置,环境切换只需一个参数
- 离线友好:零外网依赖,纯本地资源部署
- 幂等性考虑:存在性检查、重复执行安全
- 错误处理:文件缺失检测、跳过处理,不会因为单个服务失败导致整体中断
扩展性
新增一个服务只需三步:
- 在
services.sh添加一行配置 - 准备对应的Docker镜像包
- 准备对应的Helm Chart包
注意事项
这套方案有一些前提条件:
- 需预先安装:MySQL、MongoDB、K8s、Docker、JDK
- 需配置MySQL/MongoDB用户权限
- 离线镜像和Chart需提前准备
写在最后
从手动部署到自动化部署,提升的不仅是效率,更是可靠性和可维护性。Shell脚本虽然看起来"原始”,但在离线部署场景下,它的可控性和透明度反而是最大的优势。
如果你的团队也在为K8s部署头疼,不妨试试这套思路——不一定完全照搬,但模块化、版本化、离线优先的设计原则应该是通用的。
相关技术栈:Docker、Kubernetes、Helm、Shell、MySQL、MongoDB、Redis、MinIO、NFS