接收参数:环境和镜像tag
深入理解部署工具:我的一次 DevOps 改造实战

引言:为什么我们要重视部署这件事?
作为一位在互联网公司负责后端研发工具链建设的开发者,我经常会被问一个问题:“不就是个 deploy 吗?不就跑个脚本或者点个按钮的事儿?”
但如果你真的以为部署只是“执行一下”,那你就大错特错了。
今天这篇文章,我想结合我在一个实际项目中的经历,聊聊我们是如何从混乱的手动部署,逐步演进到一套自动化、标准化的部署流程,过程中踩过哪些坑,做过了哪些技术选型,又带来了什么好处。希望通过这次真实的分享,能让你对部署工具有更深的理解。
背景介绍:一次中型项目的上线危机
时间回到两年前,我在一家中型电商公司任职,团队在搭建一个新的营销平台,用于支持节日大促期间的秒杀、拼团等业务功能。这个项目预计上线时间紧张,而且要求高可用、低延迟。
项目本身是基于 Spring Boot + MySQL + Redis 构建,部署架构初步设计为 Nginx 做负载均衡,后端服务部署在多个节点上,前端使用 CDN 托管静态资源。
当时我们的部署方式如下:
- 开发同学写完代码推送到 GitLab
- 测试环境手动 Jenkins 构建并上传 artifact 到测试服务器
- 线上环境由运维同学通过 SSH 连接一台跳板机,再登录多台应用服务器,依次执行更新脚本
听起来没什么问题?但事实是:
- 发布版本经常出错(比如构建的 JAR 包没打对、配置文件遗漏)
- 一旦失败,回滚困难,容易导致服务不可用
- 多人操作时缺乏统一规范,出现过“上线一半”状态
- 新来的同事很难接手部署任务,因为没有文档和标准流程
有一次大促前灰度发布的时候,其中一个应用忘记更新配置,结果灰度流量直接打到了线上老版本,造成了严重的数据污染,整整折腾了一天才恢复。
那次之后,我们下定决心要做一次全面的部署系统升级。
问题描述:手动部署的痛点到底在哪?
我们团队经过一次复盘会,总结出几个核心问题:
| 问题 | 表现 | 影响 |
|---|---|---|
| 不一致 | 不同人执行的结果可能不一样 | 容易导致上线失败或异常行为 |
| 不可追踪 | 没有记录谁什么时候做了什么改动 | 故障排查困难 |
| 不可靠 | 依赖人为判断与经验 | 易出错、回滚慢 |
| 不透明 | 无监控/日志 | 出了问题不知道从哪里查 |
这些问题归根结底,都是缺乏部署工具与流程标准化。
于是我们开始思考:能不能借助现有的开源工具+少量自研,构建一个轻量级但高效的部署系统?
解决方案:CI/CD + 配置管理 + 自动化部署三位一体
我们决定采用“三位一体”的部署解决方案:
一、CI/CD 系统 —— Jenkins + GitLab Pipeline
虽然现在 GitHub Actions、GitLab CI、Argo CD 都很流行,但我们当时的基建还处于起步阶段,选择了最熟悉的 Jenkins 作为基础构建平台,并逐步过渡到 GitLab CI Pipeline,实现自动触发构建、打包、镜像制作等功能。
选择依据:
- Jenkins 插件丰富、生态完善,且已有一定的维护经验;
- GitLab Pipeline 编排更清晰,适合未来向 GitOps 靠拢;
- 统一触发入口,避免不同人手动生成 artifact。
二、镜像管理 —— Docker + Harbor
为了避免不同环境下的差异性问题(如 JVM 参数、环境变量、文件路径),我们决定采用 Docker 封装整个运行环境,并使用 Harbor 私有镜像仓库进行版本管理和安全扫描。
关键改进:
- 环境一致性:本地开发、测试、生产用同一套镜像;
- 快速回滚:只需切换标签即可还原至上一个稳定版本;
- 安全可控:Harbor 提供权限控制与漏洞扫描。
三、部署编排 —— Ansible + Shell 脚本封装
为了快速落地,我们在初期采用了 Ansible 来进行主机编排。Ansible 免安装的特点非常适合我们已有的物理机部署结构。
同时我们开发了一个部署脚本的封装层,用于统一接口、处理日志输出、记录部署状态,并对外暴露 RESTful API 接口给上层调用。
为何不选 Kubernetes?
因为我们当时还在向容器化迁移阶段,大量旧服务仍在裸机上运行,而 K8s 成本较高,暂未引入。
四、配置中心 —— 自研简化版
我们自己开发了一个小型的配置中心,用于统一管理各个集群的 application.yml、数据库连接串、开关参数等信息。虽然没有携程 Apollo 那么复杂的功能,但满足了基本需求。
关键代码实践:部署流程如何跑起来?
以下是我们最终使用的 GitLab CI 示例片段:
stages:
- build
- package
- deploy
build_backend:
stage: build
script:
- mvn clean package
package_docker:
stage: package
script:
- docker build -t harbor.example.com/mkt-service:${CI_COMMIT_TAG} .
- docker login -u admin -p ${HARBOR_PASSWORD}
- docker push harbor.example.com/mkt-service:${CI_COMMIT_TAG}
deploy_staging:
stage: deploy
script:
- ssh -i ~/.ssh/id_rsa user@jump-host 'cd /opt/deploy && ./deploy.sh -e staging -t ${CI_COMMIT_TAG}'
而 deploy.sh 的大致逻辑如下:
#!/bin/bash
ENV=$1
TAG=$2
# 加载对应环境的配置
source config/$ENV.env
# 停止现有容器
docker stop mkt-service || true
docker rm mkt-service || true
# 拉取新镜像并启动
docker pull harbor.example.com/mkt-service:$TAG
docker run -d --name mkt-service \
-e ENV=$ENV \
-e DB_HOST=$DB_HOST \
harbor.example.com/mkt-service:$TAG
此外,我们还开发了一个小服务,用来监听 GitLab Webhook 并主动触发部署任务,方便接入 Slack、企业微信等通知渠道。
踩坑经验分享:别让细节毁掉努力
任何一次改造都不是一帆风顺的。我们也遇到了不少问题,分享几个印象深刻的“雷”。
坑一:Dockerfile 中忽略文件过大问题
早期我们打包的时候发现镜像越来越大,后来发现是因为在 Dockerfile 中没有过滤掉 target 目录、git 历史、node_modules 文件夹等不需要的内容。
解决方案:
在 .dockerignore 中加入这些内容,减少构建体积:
.git
node_modules
target/
*.log
*.md
坑二:部署并发导致服务中断
我们在做蓝绿部署的时候,没有合理安排滚动策略,结果一次同时重启所有实例,造成几分钟内整个服务不可用。
教训总结:
- 控制并发数,建议最多同时更新一半节点;
- 引入健康检查机制,确认新实例正常后再切流量;
- 使用负载均衡器来控制流量切换。
坑三:Ansible Playbook 编写不够规范
最初我们写了很多 ad-hoc 脚本,但随着模块越来越多,难以维护。
后来我们改为结构化的 Playbook,并抽象出通用 role(如 deploy-java-app, init-mysql 等),提高了复用性和可读性。
坑四:日志和错误处理缺失
部署脚本一开始只输出成功与否,没有详细的日志跟踪和异常捕获,导致排查问题非常痛苦。
后来我们统一规范输出格式,并将部署日志集中收集到 ELK Stack 中。
实际效果与收益
这次部署系统的重构带来的提升非常明显:
| 方面 | 改进前 | 改进后 |
|---|---|---|
| 部署耗时 | 15~30分钟 | 3~5分钟 |
| 故障率 | 每周至少1次 | 几乎零故障 |
| 回滚时间 | 10分钟以上 | <1分钟 |
| 文档完备性 | 基本无文档 | 自动生成变更日志 |
| 可追溯性 | 无法追溯 | 每次部署都有流水号和负责人记录 |
更重要的是,团队协作效率显著提高,所有人都能按照标准流程完成部署工作,新人也能快速上手。
经验总结:我学到的几点实用建议
如果你也在考虑优化你的部署流程,这里有几条我亲身验证过的小建议:
不要一开始就追求完美架构
- 如果你们还没有自动化部署的基础,不妨先从简单的 Shell 脚本 + Jenkins 构建入手。
- 一步步来,先解决“一致性”和“可靠性”问题,再考虑性能和扩展性。
部署过程要留痕
- 每一次部署都应该记录日志、版本号、提交人、触发方式。
- 推荐使用部署 ID 或者 Git tag 作为唯一标识。
部署 ≠ 发布
- 部署只是把新代码放到机器上,真正的发布还需要考虑流量切换、灰度、回滚。
- 把部署和发布解耦,会让你后期更容易做高级玩法(比如 A/B 测试、金丝雀发布等)。
配置即代码,环境即镜像
- 避免硬编码的环境变量,尽量通过配置中心注入。
- 把整个运行环境打包进镜像,真正做到“所见即所得”。
建立快速失败机制
- 部署失败第一时间停下来,而不是继续推进。
- 比如可以在部署前做健康检查,在容器启动后运行探针脚本。
拥抱持续交付思想
- 部署只是一个环节,真正重要的是让代码从提交到上线整个链条都变得自动化。
- 试着把 QA、UAT、性能测试、安全扫描都纳入进去。
监控不能少
- 上线后最好搭配 Prometheus、Grafana 等工具实时观察服务状态。
- 遇到异常可以快速定位是部署问题还是业务代码问题。
写在最后:部署不只是运维的事
在我以前的认知里,部署是运维的工作,开发只需要关注代码质量就行。但现在我越来越明白,一个好的部署流程,其实是工程能力的重要体现。
它不仅决定了我们能否高效、稳定地交付产品,也影响着整个团队的研发文化。一个成熟、自动化的部署体系,能让开发人员更专注于业务创新,而不是被各种“上线问题”搞得焦头烂额。
当然,部署工具不是万能的,它只是帮助我们更好地掌控变化。真正关键的,是我们对于流程的理解、对细节的关注,以及对每一次发布的敬畏之心。
希望这篇文章能给你一些启发。如果你正在经历类似的困扰,欢迎留言交流,我们一起成长!
✨本文作者是一名有着多年 DevOps 工具链建设经验的工程师,参与过多轮部署系统重构与自动化体系建设。目前致力于推动研发效能提效工具的研发与推广,欢迎关注我的其他文章或在评论区提问交流!

评论 0