接收参数:环境和镜像tag

炫酷之思想家
2025-06-15 16:20
阅读 581

深入理解部署工具:我的一次 DevOps 改造实战

深入理解部署工具:我的一次 DevOps 改造实战

引言:为什么我们要重视部署这件事?

作为一位在互联网公司负责后端研发工具链建设的开发者,我经常会被问一个问题:“不就是个 deploy 吗?不就跑个脚本或者点个按钮的事儿?”

但如果你真的以为部署只是“执行一下”,那你就大错特错了。

今天这篇文章,我想结合我在一个实际项目中的经历,聊聊我们是如何从混乱的手动部署,逐步演进到一套自动化、标准化的部署流程,过程中踩过哪些坑,做过了哪些技术选型,又带来了什么好处。希望通过这次真实的分享,能让你对部署工具有更深的理解。


背景介绍:一次中型项目的上线危机

时间回到两年前,我在一家中型电商公司任职,团队在搭建一个新的营销平台,用于支持节日大促期间的秒杀、拼团等业务功能。这个项目预计上线时间紧张,而且要求高可用、低延迟。

项目本身是基于 Spring Boot + MySQL + Redis 构建,部署架构初步设计为 Nginx 做负载均衡,后端服务部署在多个节点上,前端使用 CDN 托管静态资源。

当时我们的部署方式如下:

  • 开发同学写完代码推送到 GitLab
  • 测试环境手动 Jenkins 构建并上传 artifact 到测试服务器
  • 线上环境由运维同学通过 SSH 连接一台跳板机,再登录多台应用服务器,依次执行更新脚本

听起来没什么问题?但事实是:

  1. 发布版本经常出错(比如构建的 JAR 包没打对、配置文件遗漏)
  2. 一旦失败,回滚困难,容易导致服务不可用
  3. 多人操作时缺乏统一规范,出现过“上线一半”状态
  4. 新来的同事很难接手部署任务,因为没有文档和标准流程

有一次大促前灰度发布的时候,其中一个应用忘记更新配置,结果灰度流量直接打到了线上老版本,造成了严重的数据污染,整整折腾了一天才恢复。

那次之后,我们下定决心要做一次全面的部署系统升级。


问题描述:手动部署的痛点到底在哪?

我们团队经过一次复盘会,总结出几个核心问题:

问题 表现 影响
不一致 不同人执行的结果可能不一样 容易导致上线失败或异常行为
不可追踪 没有记录谁什么时候做了什么改动 故障排查困难
不可靠 依赖人为判断与经验 易出错、回滚慢
不透明 无监控/日志 出了问题不知道从哪里查

这些问题归根结底,都是缺乏部署工具与流程标准化

于是我们开始思考:能不能借助现有的开源工具+少量自研,构建一个轻量级但高效的部署系统?


解决方案: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分钟
文档完备性 基本无文档 自动生成变更日志
可追溯性 无法追溯 每次部署都有流水号和负责人记录

更重要的是,团队协作效率显著提高,所有人都能按照标准流程完成部署工作,新人也能快速上手。


经验总结:我学到的几点实用建议

如果你也在考虑优化你的部署流程,这里有几条我亲身验证过的小建议:

  1. 不要一开始就追求完美架构

    • 如果你们还没有自动化部署的基础,不妨先从简单的 Shell 脚本 + Jenkins 构建入手。
    • 一步步来,先解决“一致性”和“可靠性”问题,再考虑性能和扩展性。
  2. 部署过程要留痕

    • 每一次部署都应该记录日志、版本号、提交人、触发方式。
    • 推荐使用部署 ID 或者 Git tag 作为唯一标识。
  3. 部署 ≠ 发布

    • 部署只是把新代码放到机器上,真正的发布还需要考虑流量切换、灰度、回滚。
    • 把部署和发布解耦,会让你后期更容易做高级玩法(比如 A/B 测试、金丝雀发布等)。
  4. 配置即代码,环境即镜像

    • 避免硬编码的环境变量,尽量通过配置中心注入。
    • 把整个运行环境打包进镜像,真正做到“所见即所得”。
  5. 建立快速失败机制

    • 部署失败第一时间停下来,而不是继续推进。
    • 比如可以在部署前做健康检查,在容器启动后运行探针脚本。
  6. 拥抱持续交付思想

    • 部署只是一个环节,真正重要的是让代码从提交到上线整个链条都变得自动化。
    • 试着把 QA、UAT、性能测试、安全扫描都纳入进去。
  7. 监控不能少

    • 上线后最好搭配 Prometheus、Grafana 等工具实时观察服务状态。
    • 遇到异常可以快速定位是部署问题还是业务代码问题。

写在最后:部署不只是运维的事

在我以前的认知里,部署是运维的工作,开发只需要关注代码质量就行。但现在我越来越明白,一个好的部署流程,其实是工程能力的重要体现。

它不仅决定了我们能否高效、稳定地交付产品,也影响着整个团队的研发文化。一个成熟、自动化的部署体系,能让开发人员更专注于业务创新,而不是被各种“上线问题”搞得焦头烂额。

当然,部署工具不是万能的,它只是帮助我们更好地掌控变化。真正关键的,是我们对于流程的理解、对细节的关注,以及对每一次发布的敬畏之心。

希望这篇文章能给你一些启发。如果你正在经历类似的困扰,欢迎留言交流,我们一起成长!


✨本文作者是一名有着多年 DevOps 工具链建设经验的工程师,参与过多轮部署系统重构与自动化体系建设。目前致力于推动研发效能提效工具的研发与推广,欢迎关注我的其他文章或在评论区提问交流!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝