从零到一构建部署工具:踩过的坑,踩出的路
开篇:为什么需要一个内部部署工具?

两年前,我所在的互联网公司正处于快速扩张期。随着微服务架构的普及和团队规模的扩大,原本依赖 Jenkins + Shell 脚本的手动发布流程逐渐显露出各种问题:发布版本不一致、环境差异带来的问题无法复现、发布过程中人工干预多,容易出错。
我们尝试使用了一些市面上流行的 CI/CD 工具,但因为定制化需求高、权限管理复杂等问题,始终没找到完全契合自身业务场景的方案。于是,我和小组决定自己动手开发一套轻量级、可扩展、贴合我们技术栈和运维流程的部署工具。
这篇文章不是教你如何设计部署系统,而是想讲讲我们在构建过程中的真实经历——那些踩过的坑,踩出来的经验和思路转变。
项目背景:我们的需求是什么?

公司的基础架构主要是 Java(Spring Cloud) 和 Node.js 服务,部署环境包括物理机、K8s 集群以及部分混合云平台。每个服务都有自己的构建流程,依赖也各不相同,但我们对“部署”的定义非常清晰:
- 支持多语言、多环境(dev/test/stage/prod)
- 提供统一的操作界面
- 自动执行构建、打包、部署、通知等环节
- 权限控制细粒度到服务级别
- 日志可视化,支持回滚机制
最初的想法是基于 Jenkins 插件二次开发。但调研之后发现,Jenkins 的 Pipeline DSL 强大但也复杂,对于普通研发同学不够友好。我们希望降低使用门槛,同时又不想牺牲灵活性。
最终确定采用 Python + Flask + Celery 搭建核心调度服务,结合前端 Vue 实现 UI 层交互。
真实挑战:部署不是“点一下”这么简单

挑战一:环境一致性问题频发
最开始,我们以为部署只是把代码 build 出来 scp 到服务器上执行脚本就行。结果上线初期就频频出现问题:
- “我在本地 build 好的服务,在测试环境跑不起来。”
- “这个机器上的 Python 版本不一样!”
- “为什么 dev 环境能正常运行,生产报错了?”
根本原因是我们忽略了部署前的构建隔离环境,不同节点环境状态不一致。
解决方案:引入 Docker 构建沙盒机制,将构建流程全部在统一镜像中进行。我们维护了一个 build 镜像仓库,每个项目配置指定的 Build Image(比如 Java 11 + Maven 3.6,或 Node 14),确保构建的一致性。
# 示例:build_config.yaml
project:
name: order-service
language: java
build_image: registry.example.com/build/java11-maven:latest
构建任务由 Celery Worker 执行,启动容器并注入 Git Repo 地址和分支信息。
挑战二:自动化程度低,依然需要人盯着
原本设想所有任务都可以一键执行,但在实际落地时发现:
- 某些服务部署后要等数据库迁移完成才能继续下一步
- 有服务部署后必须手动触发一次灰度流量,确认没问题后再全量生效
这些流程用简单的流程图很难表达清楚。
解决方案:我们引入了 Pipeline 编排引擎,借鉴了 Airflow 的 DAG 思路,为每项部署流程编写 YAML 定义文件,描述各个阶段的任务与依赖关系。
例如:
stages:
- stage: prepare
tasks:
- name: check_dependencies
action: script
path: ./scripts/check_db.sh
- stage: build
depends_on: prepare
tasks:
- name: run_maven_build
action: build_in_docker
image: java11-maven
command: mvn clean package
- stage: deploy
depends_on: build
tasks:
- name: deploy_to_staging
action: ssh_exec
host: staging-host01
command: |
cd /opt/app
git pull origin master
systemctl restart order-service
YAML 解析层负责解析、编排任务流,Celery 负责任务执行,一旦某个节点失败,则整条流水线暂停,自动通知负责人。
挑战三:权限模型混乱导致误操作频发
当部门多了以后,权限是个大问题。起初为了省事,我们给每个人开通 sudo 权限,结果有一次:
“不小心把生产环境的服务重启了一圈……”
惨烈教训之后,我们下决心重构了整个权限体系。
做法:参考 RBAC 模型,结合 LDAP 认证中心实现细粒度权限控制。比如,“只有 DevOps 组的人可以操作 prod 环境”,“某服务只允许对应组的成员提交部署”。
权限模块后来成为我们这套部署系统中最受欢迎的功能之一,不仅让操作更安全,也让审计变得轻松。
关键实现细节:几个值得分享的代码片段
以下是我们用来解析 YAML 流水线的核心逻辑简略版:
def parse_pipeline(config_path):
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
stages = config.get('stages', [])
for stage in stages:
tasks = stage.get('tasks', [])
depends_on = stage.get('depends_on')
dag.add_stage(name=stage['stage'], depends_on=depends_on)
for task in tasks:
dag.add_task(
stage_name=stage['stage'],
task_id=task['name'],
action=task['action'],
payload=task.get('payload', {})
)
return dag
而在 Celery Worker 中,执行任务的时候会根据 action 类型路由到具体的方法:
@celery.task
def run_task(task_info):
action_type = task_info['action']
if action_type == 'build_in_docker':
execute_docker_build(**task_info.get('options', {}))
elif action_type == 'ssh_exec':
execute_ssh_command(**task_info.get('options', {}))
# 还有更多action类型...
此外,日志实时输出我们也做了优化,使用 WebSockets 将后台执行的输出直接推送到前端,这样用户可以看到类似终端一样的日志流:
const socket = new WebSocket("ws://deploy-server/log");
socket.onmessage = function (event) {
const data = JSON.parse(event.data);
document.getElementById("log").innerHTML += `<div>${data.content}</div>`;
};
踩过的坑 & 如何跨过它们
坑一:“先写个 demo 再完善”的陷阱

一开始我们想着先做个原型出来再说,结果后面越来越难改,架构越改越重。建议:
不要为了快而忽略架构设计。哪怕只是一个内部工具,也尽量设计成可插拔、模块化的结构。后期扩展起来不会太痛苦。
坑二:低估了“可观测性”的重要性
最初没有埋点日志追踪机制,出问题查故障非常困难。后来加了 ELK(Elasticsearch + Logstash + Kibana)来做集中日志收集,并集成了告警系统。
现在遇到异常任务可以直接看到完整上下文,再也不怕甩锅大会。
坑三:过度依赖 SSH 和脚本
虽然 SSH 是个方便的选择,但缺点也很多:连接不稳定、命令错误难以调试、权限不好控制。
我们后来通过自研的 Agent 实现远程执行代理功能,Agent 只需部署在目标机器上,接收指令并执行任务,同时返回结构化日志:
GET /task?token=<uuid>
-> {"status": "success", "output": "..."}
这种方式比原始 SSH 更可控、更易于管理。
成果与收益:不只是效率提升
上线一年后,我们统计了使用情况:
- 平均每次部署时间从 25 分钟压缩到 7 分钟
- 生产误操作事故下降了 70%
- 新入职的同学可以在半天内掌握部署流程
- 配置即代码的理念被广泛接受,团队协作更加顺畅
最大的成就感来自于——以前大家提到部署就头疼,现在反而觉得这事儿挺顺手的,甚至愿意主动贡献一些小插件进来。
我的几点建议

如果你正在考虑构建或改进你们团队的部署系统,这里是我的一些建议:
不要重复造轮子,除非你真的需要差异化能力。
- 如果公司规模不大,建议优先使用开源工具如 ArgoCD、Tekton 等,节省精力。
- 如果确实有自己的特殊流程和权限模型,再考虑自研。
重视构建环境的标准化。
- 使用 Docker 或虚拟机提供一致的 Build Context,避免环境污染。
流程抽象要清晰,最好可视化。
- 不管是 YAML、图形流程图还是拖拽式编辑器,一定要让人“看得见”整个流程在干什么。
日志、监控、报警一个都不能少。
- 否则你会陷入“明明执行了,为什么没生效?”的困境。
UI 上的体验也很重要。
- 即使是内部工具,也要注重用户体验。不然没人愿意用。
从一线使用者的角度设计流程。
- 多去听听他们的抱怨,往往那就是痛点所在。
结语:做工具也是做产品
回头看看这两年折腾部署系统的这段旅程,其实远远超出了我的预期。
我们不仅仅打造了一个工具,更像是建立了一套标准、一种习惯、一份团队之间的信任基础。部署不再是“风险时刻”,而是一次可控、可预测、可追踪的过程。
有时候我会想,好的基础设施就像空气,用得舒服时你几乎感觉不到它的存在,但一旦缺失,就会寸步难行。或许这就是技术人的价值之一:让复杂的事情变简单,让重复的工作变可靠。
共勉吧,每一个还在跟部署打交道的开发者。希望这篇文章能为你带来一点启发和力量。
📝 文章首发于个人博客【TechNotes】,欢迎关注交流。
🧑💻 作者简介:一名热爱写工具的码农,现服务于某大型互联网公司 DevOps 团队。

评论 0